2026-03-05 00:04:26 -06:00
|
|
|
//go:build windows
|
|
|
|
|
// +build windows
|
|
|
|
|
|
|
|
|
|
package handlers
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
2026-05-16 23:19:46 -05:00
|
|
|
"os"
|
2026-05-17 02:35:48 -05:00
|
|
|
"runtime"
|
2026-03-05 00:04:26 -06:00
|
|
|
"strings"
|
2026-05-17 02:35:48 -05:00
|
|
|
"sync"
|
2026-03-05 00:04:26 -06:00
|
|
|
"syscall"
|
2026-05-17 02:35:48 -05:00
|
|
|
"time"
|
2026-03-05 00:04:26 -06:00
|
|
|
"unsafe"
|
|
|
|
|
|
|
|
|
|
"overlord-client/cmd/agent/wire"
|
|
|
|
|
|
|
|
|
|
"golang.org/x/sys/windows"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
modKernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
|
|
|
|
modPsapi = windows.NewLazySystemDLL("psapi.dll")
|
2026-05-17 02:35:48 -05:00
|
|
|
modNtdll = windows.NewLazySystemDLL("ntdll.dll")
|
2026-05-21 09:51:12 -05:00
|
|
|
procEnumProcesses = modPsapi.NewProc("EnumProcesses")
|
|
|
|
|
procGetProcessMemoryInfo = modPsapi.NewProc("GetProcessMemoryInfo")
|
|
|
|
|
procOpenProcess = modKernel32.NewProc("OpenProcess")
|
|
|
|
|
procGetProcessImageFileNameW = modPsapi.NewProc("GetProcessImageFileNameW")
|
|
|
|
|
procGetProcessTimes = modKernel32.NewProc("GetProcessTimes")
|
|
|
|
|
procQueryFullProcessImageNameW = modKernel32.NewProc("QueryFullProcessImageNameW")
|
2026-05-17 02:35:48 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type cpuSample struct {
|
|
|
|
|
cpuTime100ns uint64
|
|
|
|
|
sampledAt time.Time
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
cpuSamplesMu sync.Mutex
|
|
|
|
|
cpuSamples = make(map[int32]cpuSample)
|
2026-03-05 00:04:26 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type PROCESS_MEMORY_COUNTERS struct {
|
|
|
|
|
CB uint32
|
|
|
|
|
PageFaultCount uint32
|
|
|
|
|
PeakWorkingSetSize uintptr
|
|
|
|
|
WorkingSetSize uintptr
|
|
|
|
|
QuotaPeakPagedPoolUsage uintptr
|
|
|
|
|
QuotaPagedPoolUsage uintptr
|
|
|
|
|
QuotaPeakNonPagedPoolUsage uintptr
|
|
|
|
|
QuotaNonPagedPoolUsage uintptr
|
|
|
|
|
PagefileUsage uintptr
|
|
|
|
|
PeakPagefileUsage uintptr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func listProcesses() ([]wire.ProcessInfo, error) {
|
2026-05-16 23:19:46 -05:00
|
|
|
selfPID := int32(os.Getpid())
|
2026-05-17 02:35:48 -05:00
|
|
|
numCPU := runtime.NumCPU()
|
|
|
|
|
if numCPU < 1 {
|
|
|
|
|
numCPU = 1
|
|
|
|
|
}
|
2026-03-05 00:04:26 -06:00
|
|
|
var pids [4096]uint32
|
|
|
|
|
var bytesReturned uint32
|
|
|
|
|
|
|
|
|
|
ret, _, _ := procEnumProcesses.Call(
|
|
|
|
|
uintptr(unsafe.Pointer(&pids[0])),
|
|
|
|
|
uintptr(len(pids)*4),
|
|
|
|
|
uintptr(unsafe.Pointer(&bytesReturned)),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if ret == 0 {
|
|
|
|
|
return nil, fmt.Errorf("EnumProcesses failed")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
numProcesses := int(bytesReturned / 4)
|
|
|
|
|
processes := make([]wire.ProcessInfo, 0, numProcesses)
|
|
|
|
|
|
|
|
|
|
for i := 0; i < numProcesses; i++ {
|
|
|
|
|
pid := int32(pids[i])
|
|
|
|
|
if pid == 0 {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-18 21:34:35 -05:00
|
|
|
info := queryProcess(pid, selfPID, numCPU)
|
|
|
|
|
if info == nil {
|
2026-03-05 00:04:26 -06:00
|
|
|
continue
|
|
|
|
|
}
|
2026-05-18 21:34:35 -05:00
|
|
|
processes = append(processes, *info)
|
2026-03-05 00:04:26 -06:00
|
|
|
}
|
|
|
|
|
|
2026-05-17 02:35:48 -05:00
|
|
|
cpuSamplesMu.Lock()
|
|
|
|
|
live := make(map[int32]struct{}, len(processes))
|
|
|
|
|
for _, p := range processes {
|
|
|
|
|
live[p.PID] = struct{}{}
|
|
|
|
|
}
|
|
|
|
|
for pid := range cpuSamples {
|
|
|
|
|
if _, ok := live[pid]; !ok {
|
|
|
|
|
delete(cpuSamples, pid)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
cpuSamplesMu.Unlock()
|
|
|
|
|
|
2026-03-05 00:04:26 -06:00
|
|
|
return processes, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-18 21:34:35 -05:00
|
|
|
func queryProcess(pid, selfPID int32, numCPU int) *wire.ProcessInfo {
|
|
|
|
|
handle, _, _ := procOpenProcess.Call(
|
|
|
|
|
windows.PROCESS_QUERY_INFORMATION|windows.PROCESS_VM_READ,
|
|
|
|
|
0,
|
|
|
|
|
uintptr(pid),
|
|
|
|
|
)
|
|
|
|
|
if handle == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
defer windows.CloseHandle(windows.Handle(handle))
|
|
|
|
|
|
|
|
|
|
var filename [windows.MAX_PATH]uint16
|
|
|
|
|
ret, _, _ := procGetProcessImageFileNameW.Call(
|
|
|
|
|
handle,
|
|
|
|
|
uintptr(unsafe.Pointer(&filename[0])),
|
|
|
|
|
uintptr(len(filename)),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
name := "Unknown"
|
|
|
|
|
if ret != 0 {
|
|
|
|
|
name = syscall.UTF16ToString(filename[:])
|
|
|
|
|
for i := len(name) - 1; i >= 0; i-- {
|
|
|
|
|
if name[i] == '\\' || name[i] == '/' {
|
|
|
|
|
name = name[i+1:]
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-21 09:51:12 -05:00
|
|
|
exePath := ""
|
|
|
|
|
var dosPath [windows.MAX_PATH]uint16
|
|
|
|
|
dosPathLen := uint32(windows.MAX_PATH)
|
|
|
|
|
ret2, _, _ := procQueryFullProcessImageNameW.Call(
|
|
|
|
|
handle, 0,
|
|
|
|
|
uintptr(unsafe.Pointer(&dosPath[0])),
|
|
|
|
|
uintptr(unsafe.Pointer(&dosPathLen)),
|
|
|
|
|
)
|
|
|
|
|
if ret2 != 0 && dosPathLen > 0 {
|
|
|
|
|
exePath = syscall.UTF16ToString(dosPath[:dosPathLen])
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-18 21:34:35 -05:00
|
|
|
var memCounters PROCESS_MEMORY_COUNTERS
|
|
|
|
|
memCounters.CB = uint32(unsafe.Sizeof(memCounters))
|
|
|
|
|
memory := uint64(0)
|
|
|
|
|
|
|
|
|
|
ret, _, _ = procGetProcessMemoryInfo.Call(
|
|
|
|
|
handle,
|
|
|
|
|
uintptr(unsafe.Pointer(&memCounters)),
|
|
|
|
|
uintptr(memCounters.CB),
|
|
|
|
|
)
|
|
|
|
|
if ret != 0 {
|
|
|
|
|
memory = uint64(memCounters.WorkingSetSize)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cpu := 0.0
|
|
|
|
|
var creation, exit, kernel, user windows.Filetime
|
|
|
|
|
ret, _, _ = procGetProcessTimes.Call(
|
|
|
|
|
handle,
|
|
|
|
|
uintptr(unsafe.Pointer(&creation)),
|
|
|
|
|
uintptr(unsafe.Pointer(&exit)),
|
|
|
|
|
uintptr(unsafe.Pointer(&kernel)),
|
|
|
|
|
uintptr(unsafe.Pointer(&user)),
|
|
|
|
|
)
|
|
|
|
|
if ret != 0 {
|
|
|
|
|
cpuTotal := (uint64(kernel.HighDateTime)<<32 | uint64(kernel.LowDateTime)) +
|
|
|
|
|
(uint64(user.HighDateTime)<<32 | uint64(user.LowDateTime))
|
|
|
|
|
now := time.Now()
|
|
|
|
|
cpuSamplesMu.Lock()
|
|
|
|
|
if prev, ok := cpuSamples[pid]; ok {
|
|
|
|
|
wallDelta := now.Sub(prev.sampledAt).Seconds()
|
|
|
|
|
if wallDelta > 0 && cpuTotal >= prev.cpuTime100ns {
|
|
|
|
|
cpuDelta := float64(cpuTotal-prev.cpuTime100ns) / 1e7
|
|
|
|
|
cpu = (cpuDelta / wallDelta) / float64(numCPU) * 100.0
|
|
|
|
|
if cpu < 0 {
|
|
|
|
|
cpu = 0
|
|
|
|
|
} else if cpu > 100 {
|
|
|
|
|
cpu = 100
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
cpuSamples[pid] = cpuSample{cpuTime100ns: cpuTotal, sampledAt: now}
|
|
|
|
|
cpuSamplesMu.Unlock()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var pbi windows.PROCESS_BASIC_INFORMATION
|
|
|
|
|
ppid := int32(0)
|
|
|
|
|
if err := windows.NtQueryInformationProcess(windows.Handle(handle), windows.ProcessBasicInformation, unsafe.Pointer(&pbi), uint32(unsafe.Sizeof(pbi)), nil); err == nil {
|
|
|
|
|
ppid = int32(pbi.InheritedFromUniqueProcessId)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
username := "System"
|
|
|
|
|
var token windows.Token
|
|
|
|
|
tokenOpen := false
|
|
|
|
|
if err := windows.OpenProcessToken(windows.Handle(handle), windows.TOKEN_QUERY, &token); err == nil {
|
|
|
|
|
tokenOpen = true
|
|
|
|
|
defer token.Close()
|
|
|
|
|
tokenUser, err := token.GetTokenUser()
|
|
|
|
|
if err == nil {
|
|
|
|
|
account, domain, _, err := tokenUser.User.Sid.LookupAccount("")
|
|
|
|
|
if err == nil {
|
|
|
|
|
if domain != "" {
|
|
|
|
|
username = domain + "\\" + account
|
|
|
|
|
} else {
|
|
|
|
|
username = account
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
procType := "other"
|
|
|
|
|
usernameLower := strings.ToLower(username)
|
|
|
|
|
nameLower := strings.ToLower(name)
|
|
|
|
|
|
|
|
|
|
if pid <= 4 || nameLower == "system" || nameLower == "registry" {
|
|
|
|
|
procType = "system"
|
|
|
|
|
} else if strings.Contains(usernameLower, "system") ||
|
|
|
|
|
strings.Contains(usernameLower, "local service") ||
|
|
|
|
|
strings.Contains(usernameLower, "network service") ||
|
|
|
|
|
usernameLower == "system" {
|
|
|
|
|
procType = "service"
|
|
|
|
|
} else if tokenOpen {
|
|
|
|
|
currentUser, err := windows.GetCurrentProcessToken().GetTokenUser()
|
|
|
|
|
if err == nil {
|
|
|
|
|
tokenUser, err := token.GetTokenUser()
|
|
|
|
|
if err == nil && tokenUser.User.Sid.Equals(currentUser.User.Sid) {
|
|
|
|
|
procType = "own"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &wire.ProcessInfo{
|
|
|
|
|
PID: pid,
|
|
|
|
|
PPID: ppid,
|
|
|
|
|
Name: name,
|
2026-05-21 09:51:12 -05:00
|
|
|
ExePath: exePath,
|
2026-05-18 21:34:35 -05:00
|
|
|
CPU: cpu,
|
|
|
|
|
Memory: memory,
|
|
|
|
|
Username: username,
|
|
|
|
|
Type: procType,
|
|
|
|
|
Self: pid == selfPID,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 00:04:26 -06:00
|
|
|
func killProcess(pid int32) error {
|
|
|
|
|
handle, err := windows.OpenProcess(windows.PROCESS_TERMINATE, false, uint32(pid))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
defer windows.CloseHandle(handle)
|
|
|
|
|
|
|
|
|
|
return windows.TerminateProcess(handle, 1)
|
|
|
|
|
}
|
2026-05-17 02:35:48 -05:00
|
|
|
|
|
|
|
|
func suspendProcess(pid int32) error {
|
|
|
|
|
procNtSuspendProcess := modNtdll.NewProc("NtSuspendProcess")
|
|
|
|
|
handle, err := windows.OpenProcess(windows.PROCESS_SUSPEND_RESUME, false, uint32(pid))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
defer windows.CloseHandle(handle)
|
|
|
|
|
|
|
|
|
|
ret, _, _ := procNtSuspendProcess.Call(uintptr(handle))
|
|
|
|
|
if ret != 0 {
|
|
|
|
|
return fmt.Errorf("NtSuspendProcess failed with status 0x%x", ret)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func resumeProcess(pid int32) error {
|
|
|
|
|
procNtResumeProcess := modNtdll.NewProc("NtResumeProcess")
|
|
|
|
|
handle, err := windows.OpenProcess(windows.PROCESS_SUSPEND_RESUME, false, uint32(pid))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
defer windows.CloseHandle(handle)
|
|
|
|
|
|
|
|
|
|
ret, _, _ := procNtResumeProcess.Call(uintptr(handle))
|
|
|
|
|
if ret != 0 {
|
|
|
|
|
return fmt.Errorf("NtResumeProcess failed with status 0x%x", ret)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|