mirror of
https://github.com/moonD4rk/HackBrowserData.git
synced 2026-05-19 18:58:03 +02:00
refactor(windows): split Windows code into winapi (#575)
This commit is contained in:
+12
-178
@@ -6,68 +6,17 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"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 = 0x0001
|
||||
)
|
||||
|
||||
// systemHandleTableEntryInfoEx represents SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.
|
||||
// This is the extended version returned by SystemExtendedHandleInformation (class 64).
|
||||
//
|
||||
// Layout (64-bit Windows):
|
||||
//
|
||||
// PVOID Object; // 8 bytes
|
||||
// ULONG_PTR UniqueProcessId; // 8 bytes
|
||||
// ULONG_PTR HandleValue; // 8 bytes
|
||||
// ULONG GrantedAccess; // 4 bytes
|
||||
// USHORT CreatorBackTraceIndex; // 2 bytes
|
||||
// USHORT ObjectTypeIndex; // 2 bytes
|
||||
// ULONG HandleAttributes; // 4 bytes
|
||||
// ULONG Reserved; // 4 bytes
|
||||
// Total: 40 bytes on 64-bit
|
||||
type systemHandleTableEntryInfoEx struct {
|
||||
Object uintptr
|
||||
UniqueProcessID uintptr // ULONG_PTR: safe for PID > 65535
|
||||
HandleValue uintptr // ULONG_PTR: safe for large handle values
|
||||
GrantedAccess uint32
|
||||
CreatorBackTraceIndex uint16
|
||||
ObjectTypeIndex uint16
|
||||
HandleAttributes uint32
|
||||
Reserved uint32
|
||||
}
|
||||
|
||||
var (
|
||||
ntdll = windows.NewLazySystemDLL("ntdll.dll")
|
||||
procNtQuerySystemInformation = ntdll.NewProc("NtQuerySystemInformation")
|
||||
|
||||
kernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||
procGetFileType = kernel32.NewProc("GetFileType")
|
||||
procGetFinalPathNameByHandleW = kernel32.NewProc("GetFinalPathNameByHandleW")
|
||||
procCreateFileMappingW = kernel32.NewProc("CreateFileMappingW")
|
||||
procMapViewOfFile = kernel32.NewProc("MapViewOfFile")
|
||||
procUnmapViewOfFile = kernel32.NewProc("UnmapViewOfFile")
|
||||
procGetFileSizeEx = kernel32.NewProc("GetFileSizeEx")
|
||||
"github.com/moond4rk/hackbrowserdata/utils/winapi"
|
||||
)
|
||||
|
||||
// copyLocked copies a file that is locked by another process (e.g., Chrome's
|
||||
// Cookies database with PRAGMA locking_mode=EXCLUSIVE).
|
||||
//
|
||||
// Approach: DuplicateHandle + FileMapping
|
||||
// 1. Enumerate all open file handles via NtQuerySystemInformation(SystemExtendedHandleInformation)
|
||||
// 1. Enumerate all open file handles via NtQuerySystemInformation
|
||||
// 2. Find the handle matching the target file path
|
||||
// 3. Duplicate that handle into our process via DuplicateHandle
|
||||
// 4. Read file content through memory-mapped I/O (CreateFileMapping + MapViewOfFile)
|
||||
@@ -100,7 +49,7 @@ func findFileHandle(targetPath string) (windows.Handle, error) {
|
||||
targetSuffix := extractStableSuffix(targetPath)
|
||||
currentProcess := windows.CurrentProcess()
|
||||
|
||||
handles, err := querySystemHandles()
|
||||
handles, err := winapi.QuerySystemHandles()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -133,14 +82,13 @@ func findFileHandle(targetPath string) (windows.Handle, error) {
|
||||
}
|
||||
|
||||
// Verify it's a disk file (not a pipe, device, etc.)
|
||||
fileType, _, _ := procGetFileType.Call(uintptr(dupHandle))
|
||||
if fileType != fileTypeDisk {
|
||||
if winapi.GetFileType(dupHandle) != winapi.FileTypeDisk {
|
||||
_ = windows.CloseHandle(dupHandle)
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the file path and check if it matches our target
|
||||
name, err := getFinalPathName(dupHandle)
|
||||
name, err := winapi.GetFinalPathName(dupHandle)
|
||||
if err != nil {
|
||||
_ = windows.CloseHandle(dupHandle)
|
||||
continue
|
||||
@@ -155,101 +103,15 @@ func findFileHandle(targetPath string) (windows.Handle, error) {
|
||||
return 0, fmt.Errorf("no process has file open: %s", targetPath)
|
||||
}
|
||||
|
||||
// querySystemHandles calls NtQuerySystemInformation with
|
||||
// SystemExtendedHandleInformation (class 64) to enumerate all open handles.
|
||||
func querySystemHandles() ([]systemHandleTableEntryInfoEx, error) {
|
||||
bufSize := uint32(4 * 1024 * 1024) // start at 4 MB
|
||||
|
||||
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 > 256*1024*1024 {
|
||||
return nil, fmt.Errorf("handle info buffer exceeded 256 MB")
|
||||
}
|
||||
continue
|
||||
}
|
||||
if ret != 0 {
|
||||
return nil, fmt.Errorf("NtQuerySystemInformation returned 0x%x", ret)
|
||||
}
|
||||
|
||||
// Parse: first field is NumberOfHandles (ULONG_PTR), then array of entries
|
||||
// On 64-bit: ULONG_PTR = 8 bytes
|
||||
numberOfHandles := *(*uintptr)(unsafe.Pointer(&buf[0]))
|
||||
if numberOfHandles == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
count := int(numberOfHandles)
|
||||
// Entries start after NumberOfHandles + Reserved (both ULONG_PTR = 16 bytes total)
|
||||
const headerSize = unsafe.Sizeof(uintptr(0)) * 2
|
||||
entrySize := unsafe.Sizeof(systemHandleTableEntryInfoEx{})
|
||||
|
||||
// Validate buffer bounds
|
||||
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([]systemHandleTableEntryInfoEx, count)
|
||||
for i := 0; i < count; i++ {
|
||||
src := unsafe.Pointer(uintptr(unsafe.Pointer(&buf[0])) + headerSize + uintptr(i)*entrySize)
|
||||
entries[i] = *(*systemHandleTableEntryInfoEx)(src)
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
}
|
||||
|
||||
// getFinalPathName returns the normalized file path for a file handle.
|
||||
func getFinalPathName(handle windows.Handle) (string, error) {
|
||||
size := 512
|
||||
for {
|
||||
buf := make([]uint16, size)
|
||||
n, _, err := procGetFinalPathNameByHandleW.Call(
|
||||
uintptr(handle),
|
||||
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) {
|
||||
// Buffer too small, retry with required size
|
||||
size = int(n)
|
||||
continue
|
||||
}
|
||||
|
||||
path := windows.UTF16ToString(buf[:n])
|
||||
// Remove \\?\ prefix added by GetFinalPathNameByHandle
|
||||
path = strings.TrimPrefix(path, `\\?\`)
|
||||
return path, nil
|
||||
}
|
||||
}
|
||||
|
||||
// readFileContent reads file content from a duplicated handle.
|
||||
// It uses FileMapping first (CreateFileMapping + MapViewOfFile), which reads
|
||||
// from the OS kernel's file cache — this includes WAL data that Chrome has
|
||||
// written but not yet checkpointed to the main file. Falls back to ReadFile
|
||||
// if FileMapping fails.
|
||||
func readFileContent(handle windows.Handle) ([]byte, error) {
|
||||
// Get file size
|
||||
var fileSize int64
|
||||
ret, _, sizeErr := procGetFileSizeEx.Call(
|
||||
uintptr(handle),
|
||||
uintptr(unsafe.Pointer(&fileSize)),
|
||||
)
|
||||
if ret == 0 {
|
||||
return nil, fmt.Errorf("GetFileSizeEx: %w", sizeErr)
|
||||
fileSize, err := winapi.GetFileSizeEx(handle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if fileSize == 0 {
|
||||
return nil, fmt.Errorf("file is empty")
|
||||
@@ -258,12 +120,13 @@ func readFileContent(handle windows.Handle) ([]byte, error) {
|
||||
size := int(fileSize)
|
||||
|
||||
// Try FileMapping first — reads from kernel file cache, includes WAL data
|
||||
if data, err := readViaFileMapping(handle, size); err == nil {
|
||||
if data, err := winapi.MapFile(handle, size); err == nil {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// FileMapping failed, fall back to ReadFile
|
||||
// Seek to beginning first — the handle's file pointer may be at an arbitrary position
|
||||
// FileMapping failed, fall back to ReadFile.
|
||||
// Seek to beginning first — the handle's file pointer may be at an
|
||||
// arbitrary position.
|
||||
if _, err := windows.Seek(handle, 0, 0); err != nil {
|
||||
return nil, fmt.Errorf("seek to start: %w", err)
|
||||
}
|
||||
@@ -275,35 +138,6 @@ func readFileContent(handle windows.Handle) ([]byte, error) {
|
||||
return data[:bytesRead], nil
|
||||
}
|
||||
|
||||
// readViaFileMapping reads file content using CreateFileMapping + MapViewOfFile.
|
||||
func readViaFileMapping(handle windows.Handle, size int) ([]byte, error) {
|
||||
mapping, _, err := procCreateFileMappingW.Call(
|
||||
uintptr(handle),
|
||||
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
|
||||
}
|
||||
|
||||
// extractStableSuffix extracts a path suffix that is stable across short/long
|
||||
// path name variations. It finds "AppData" in the path and returns everything
|
||||
// after "AppData\Local\" or "AppData\Roaming\" in lowercase.
|
||||
|
||||
Reference in New Issue
Block a user