Files
AetherForge/server/internal/builder/uninstall.go
AetherForge 5fc601b564 feat: fleet ops, KEV scan, tunnels, beacon fallback, persistence
Extend owned-fleet control with scheduled tasks, audit log, file browser,
HTTPS beacon when WS drops, protocol tunnels, registry/autostart forge
options, KEV exposure in full sys check with Telegram alerts, and UI/tests.
2026-06-04 09:34:33 -07:00

155 lines
4.9 KiB
Go

package builder
import (
"fmt"
"os"
"path/filepath"
"strings"
)
func persistenceKeyName(req *BuildRequest) string {
if req.StealthMode {
name := strings.TrimSpace(req.ProcessName)
if name == "" {
name = sanitizeFileName(req.WorkerName)
}
return name
}
name := sanitizeFileName(req.WorkerName)
if name == "" {
return "CryptoMinerAgent"
}
return "CryptoMiner-" + name
}
func effectiveProcessName(req *BuildRequest) string {
if strings.TrimSpace(req.ProcessName) != "" {
return strings.TrimSpace(req.ProcessName)
}
name := sanitizeFileName(req.WorkerName)
if name == "" {
return "CryptoMinerWorker"
}
return name
}
func expandInstallRelativePath(req *BuildRequest, buildID string) string {
rel := strings.TrimSpace(req.InstallRelativePath)
if rel == "" {
rel = "CryptoMiner/{worker}-{build_short}"
}
shortBuild := buildID
if len(shortBuild) > 8 {
shortBuild = shortBuild[:8]
}
replacer := strings.NewReplacer(
"{worker}", sanitizeFileName(req.WorkerName),
"{build}", sanitizeFileName(buildID),
"{build_short}", sanitizeFileName(shortBuild),
"{process}", effectiveProcessName(req),
)
return strings.ReplaceAll(replacer.Replace(rel), "/", `\`)
}
func resolveInstallBasePS(req *BuildRequest) string {
switch strings.ToLower(strings.TrimSpace(req.InstallBase)) {
case "appdata":
return "$env:APPDATA"
case "programdata":
return "$env:ProgramData"
case "userprofile":
return "$env:USERPROFILE"
case "temp":
return "if ($env:TEMP) { $env:TEMP } else { $env:TMP }"
case "custom":
custom := strings.TrimSpace(req.InstallCustomBase)
custom = strings.ReplaceAll(custom, "'", "''")
return fmt.Sprintf("'%s'", custom)
default:
return "$env:LOCALAPPDATA"
}
}
func generateUninstallScript(buildID string, req *BuildRequest) string {
processName := effectiveProcessName(req)
persistenceKey := persistenceKeyName(req)
installRel := expandInstallRelativePath(req, buildID)
installBase := resolveInstallBasePS(req)
firewallBool := "$false"
if req.FirewallExclusion {
firewallBool = "$true"
}
pauseBool := "$false"
if !req.StealthMode {
pauseBool = "$true"
}
return fmt.Sprintf(`# AetherForge Miner Uninstaller
# Worker: %s
# Generated alongside forged installer — run as the same Windows user who installed the miner.
$ErrorActionPreference = 'SilentlyContinue'
$ProcessName = '%s'
$PersistenceKey = '%s'
$InstallBase = %s
$InstallRel = '%s'
$ExpectedInstallDir = Join-Path $InstallBase $InstallRel
$ExpectedExe = Join-Path $ExpectedInstallDir ($ProcessName + '.exe')
Write-Host "Stopping miner process..."
Get-Process -Name $ProcessName -ErrorAction SilentlyContinue | Stop-Process -Force
$InstallDir = $ExpectedInstallDir
$InstalledTxt = Join-Path $ExpectedInstallDir 'installed.txt'
if (Test-Path $InstalledTxt) {
$content = Get-Content $InstalledTxt -Raw
if ($content -match 'install_dir=(.+)') {
$parsed = $Matches[1].Trim()
if ($parsed) { $InstallDir = $parsed }
}
if ($content -match 'installed_exe=(.+)') {
$parsedExe = $Matches[1].Trim()
if ($parsedExe) { $ExpectedExe = $parsedExe }
}
}
if (Test-Path $ExpectedExe) {
Get-Process | Where-Object { $_.Path -eq $ExpectedExe } | Stop-Process -Force
}
Write-Host "Removing persistence..."
Remove-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Run' -Name $PersistenceKey -ErrorAction SilentlyContinue
Unregister-ScheduledTask -TaskName $PersistenceKey -Confirm:$false -ErrorAction SilentlyContinue
Unregister-ScheduledTask -TaskName ($PersistenceKey + '-Boot') -Confirm:$false -ErrorAction SilentlyContinue
Unregister-ScheduledTask -TaskName ($PersistenceKey + '-Logon') -Confirm:$false -ErrorAction SilentlyContinue
$StartupLnk = Join-Path $env:APPDATA 'Microsoft\Windows\Start Menu\Programs\Startup\' ($PersistenceKey + '.lnk')
if (Test-Path $StartupLnk) { Remove-Item -LiteralPath $StartupLnk -Force }
if (%s) {
Write-Host "Removing Windows Firewall rules..."
Remove-NetFirewallRule -DisplayName ('AetherForge ' + $PersistenceKey + ' In') -ErrorAction SilentlyContinue
Remove-NetFirewallRule -DisplayName ('AetherForge ' + $PersistenceKey + ' Out') -ErrorAction SilentlyContinue
}
Write-Host "Removing install directory: $InstallDir"
if ($InstallDir -and (Test-Path $InstallDir)) {
Remove-Item -LiteralPath $InstallDir -Recurse -Force
}
Write-Host "Done. Miner removed."
if (%s) { Read-Host 'Press Enter to close' }
`, req.WorkerName, processName, persistenceKey, installBase, strings.ReplaceAll(installRel, "'", "''"), firewallBool, pauseBool)
}
func (h *Handler) writeUninstallScript(buildDir string, buildID string, req *BuildRequest) (fileName, filePath string, err error) {
fileName = fmt.Sprintf("uninstall-%s.ps1", sanitizeFileName(req.WorkerName))
filePath = filepath.Join(buildDir, fileName)
content := generateUninstallScript(buildID, req)
if err := os.WriteFile(filePath, []byte(content), 0644); err != nil {
return "", "", err
}
return fileName, filePath, nil
}