mirror of
https://github.com/moonD4rk/HackBrowserData.git
synced 2026-05-19 18:58:03 +02:00
216 lines
6.5 KiB
Go
216 lines
6.5 KiB
Go
//go:build windows
|
|
|
|
package winapi
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
const (
|
|
// systemExtendedHandleInformation is the information class for
|
|
// NtQuerySystemInformation that returns SYSTEM_HANDLE_INFORMATION_EX.
|
|
// This is the 64-bit safe version (class 64) — UniqueProcessId is
|
|
// ULONG_PTR instead of USHORT, avoiding PID truncation on 64-bit Windows.
|
|
systemExtendedHandleInformation = 64
|
|
|
|
statusInfoLengthMismatch = 0xC0000004
|
|
|
|
fileMapRead = 0x0004
|
|
pageReadonly = 0x02
|
|
|
|
// FileTypeDisk is the GetFileType return value for a normal disk file.
|
|
FileTypeDisk uint32 = 0x0001
|
|
|
|
maxHandleBufferSize = 256 * 1024 * 1024
|
|
initialHandleBuffer = 4 * 1024 * 1024
|
|
)
|
|
|
|
var (
|
|
procNtQuerySystemInformation = Ntdll.NewProc("NtQuerySystemInformation")
|
|
procGetFileType = Kernel32.NewProc("GetFileType")
|
|
procGetFinalPathNameByHandleW = Kernel32.NewProc("GetFinalPathNameByHandleW")
|
|
procCreateFileMappingW = Kernel32.NewProc("CreateFileMappingW")
|
|
procMapViewOfFile = Kernel32.NewProc("MapViewOfFile")
|
|
procUnmapViewOfFile = Kernel32.NewProc("UnmapViewOfFile")
|
|
procGetFileSizeEx = Kernel32.NewProc("GetFileSizeEx")
|
|
)
|
|
|
|
// SystemHandleEntry mirrors SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX, the extended
|
|
// entry returned by SystemExtendedHandleInformation (class 64).
|
|
//
|
|
// Layout on 64-bit Windows (40 bytes):
|
|
//
|
|
// PVOID Object;
|
|
// ULONG_PTR UniqueProcessId;
|
|
// ULONG_PTR HandleValue;
|
|
// ULONG GrantedAccess;
|
|
// USHORT CreatorBackTraceIndex;
|
|
// USHORT ObjectTypeIndex;
|
|
// ULONG HandleAttributes;
|
|
// ULONG Reserved;
|
|
type SystemHandleEntry struct {
|
|
Object uintptr
|
|
UniqueProcessID uintptr
|
|
HandleValue uintptr
|
|
GrantedAccess uint32
|
|
CreatorBackTraceIndex uint16
|
|
ObjectTypeIndex uint16
|
|
HandleAttributes uint32
|
|
Reserved uint32
|
|
}
|
|
|
|
// QuerySystemHandles enumerates all open handles system-wide via
|
|
// NtQuerySystemInformation(SystemExtendedHandleInformation). The buffer
|
|
// size grows on STATUS_INFO_LENGTH_MISMATCH until it succeeds or exceeds
|
|
// the safety cap.
|
|
func QuerySystemHandles() ([]SystemHandleEntry, error) {
|
|
bufSize := uint32(initialHandleBuffer)
|
|
|
|
for {
|
|
buf := make([]byte, bufSize)
|
|
var returnLength uint32
|
|
|
|
ret, _, _ := procNtQuerySystemInformation.Call(
|
|
systemExtendedHandleInformation,
|
|
uintptr(unsafe.Pointer(&buf[0])),
|
|
uintptr(bufSize),
|
|
uintptr(unsafe.Pointer(&returnLength)),
|
|
)
|
|
|
|
if ret == statusInfoLengthMismatch {
|
|
bufSize *= 2
|
|
if bufSize > maxHandleBufferSize {
|
|
return nil, fmt.Errorf("handle info buffer exceeded %d bytes", maxHandleBufferSize)
|
|
}
|
|
continue
|
|
}
|
|
if ret != 0 {
|
|
return nil, fmt.Errorf("NtQuerySystemInformation returned 0x%x", ret)
|
|
}
|
|
|
|
// Header on 64-bit: NumberOfHandles (ULONG_PTR) + Reserved (ULONG_PTR) = 16 bytes.
|
|
numberOfHandles := *(*uintptr)(unsafe.Pointer(&buf[0]))
|
|
if numberOfHandles == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
count := int(numberOfHandles)
|
|
const headerSize = unsafe.Sizeof(uintptr(0)) * 2
|
|
entrySize := unsafe.Sizeof(SystemHandleEntry{})
|
|
|
|
required := headerSize + uintptr(count)*entrySize
|
|
if required > uintptr(len(buf)) {
|
|
return nil, fmt.Errorf("buffer too small: need %d, have %d", required, len(buf))
|
|
}
|
|
|
|
entries := make([]SystemHandleEntry, count)
|
|
for i := 0; i < count; i++ {
|
|
src := unsafe.Pointer(uintptr(unsafe.Pointer(&buf[0])) + headerSize + uintptr(i)*entrySize)
|
|
entries[i] = *(*SystemHandleEntry)(src)
|
|
}
|
|
return entries, nil
|
|
}
|
|
}
|
|
|
|
// GetFileType returns the Windows FileType for h (e.g., FileTypeDisk).
|
|
func GetFileType(h windows.Handle) uint32 {
|
|
t, _, _ := procGetFileType.Call(uintptr(h))
|
|
return uint32(t)
|
|
}
|
|
|
|
// GetFileSizeEx returns the size of the file referenced by h.
|
|
func GetFileSizeEx(h windows.Handle) (int64, error) {
|
|
var sz int64
|
|
r, _, err := procGetFileSizeEx.Call(uintptr(h), uintptr(unsafe.Pointer(&sz)))
|
|
if r == 0 {
|
|
return 0, fmt.Errorf("GetFileSizeEx: %w", err)
|
|
}
|
|
return sz, nil
|
|
}
|
|
|
|
// ExpandEnvString is the Go-friendly wrapper around
|
|
// kernel32!ExpandEnvironmentStringsW. Use it when you need to resolve
|
|
// Windows-style %VAR% placeholders — Go's stdlib os.ExpandEnv only
|
|
// recognizes Unix-style $VAR / ${VAR} and leaves %VAR% untouched.
|
|
func ExpandEnvString(s string) (string, error) {
|
|
src, err := windows.UTF16PtrFromString(s)
|
|
if err != nil {
|
|
return "", fmt.Errorf("ExpandEnvString: %w", err)
|
|
}
|
|
// 4 KB of UTF-16 easily covers MAX_PATH-bounded install locations.
|
|
buf := make([]uint16, 4096)
|
|
n, err := windows.ExpandEnvironmentStrings(src, &buf[0], uint32(len(buf)))
|
|
if n == 0 {
|
|
return "", fmt.Errorf("ExpandEnvironmentStringsW: %w", err)
|
|
}
|
|
if int(n) > len(buf) {
|
|
// Buffer was too small — retry with exact size.
|
|
buf = make([]uint16, n)
|
|
n, err = windows.ExpandEnvironmentStrings(src, &buf[0], uint32(len(buf)))
|
|
if n == 0 {
|
|
return "", fmt.Errorf("ExpandEnvironmentStringsW: %w", err)
|
|
}
|
|
}
|
|
return windows.UTF16ToString(buf[:n]), nil
|
|
}
|
|
|
|
// GetFinalPathName returns the normalized file path for h, with the
|
|
// \\?\ prefix stripped.
|
|
func GetFinalPathName(h windows.Handle) (string, error) {
|
|
size := 512
|
|
for {
|
|
buf := make([]uint16, size)
|
|
n, _, err := procGetFinalPathNameByHandleW.Call(
|
|
uintptr(h),
|
|
uintptr(unsafe.Pointer(&buf[0])),
|
|
uintptr(len(buf)),
|
|
0, // FILE_NAME_NORMALIZED
|
|
)
|
|
if n == 0 {
|
|
return "", fmt.Errorf("GetFinalPathNameByHandle: %w", err)
|
|
}
|
|
if int(n) > len(buf) {
|
|
size = int(n)
|
|
continue
|
|
}
|
|
path := windows.UTF16ToString(buf[:n])
|
|
return strings.TrimPrefix(path, `\\?\`), nil
|
|
}
|
|
}
|
|
|
|
// MapFile creates a read-only file mapping over h, copies the first
|
|
// size bytes into a Go-owned slice, and releases the mapping. Reads go
|
|
// through the OS kernel's file cache, which includes SQLite WAL data
|
|
// that has not yet been checkpointed into the main file.
|
|
func MapFile(h windows.Handle, size int) ([]byte, error) {
|
|
mapping, _, err := procCreateFileMappingW.Call(
|
|
uintptr(h),
|
|
0, pageReadonly,
|
|
0, 0, 0,
|
|
)
|
|
if mapping == 0 {
|
|
return nil, fmt.Errorf("CreateFileMapping: %w", err)
|
|
}
|
|
defer windows.CloseHandle(windows.Handle(mapping))
|
|
|
|
viewPtr, _, err := procMapViewOfFile.Call(
|
|
mapping, fileMapRead,
|
|
0, 0, 0,
|
|
)
|
|
if viewPtr == 0 {
|
|
return nil, fmt.Errorf("MapViewOfFile: %w", err)
|
|
}
|
|
defer procUnmapViewOfFile.Call(viewPtr)
|
|
|
|
// viewPtr is a valid pointer from MapViewOfFile syscall.
|
|
// go vet flags this as "possible misuse of unsafe.Pointer" but it's
|
|
// correct usage for Windows memory-mapped I/O.
|
|
data := make([]byte, size)
|
|
copy(data, (*[1 << 30]byte)(unsafe.Pointer(viewPtr))[:size]) //nolint:govet
|
|
return data, nil
|
|
}
|