├── LICENSE ├── README.md ├── examples └── example.go ├── handle.go ├── handle_386.go ├── handle_amd64.go ├── inspector.go ├── nativethread.go ├── queryobject.c ├── queryobject.go ├── queryobject.h └── querysystemhandles.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Marcel Gebhardt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-handle 2 | Iterate over Windows Handles 3 | 4 | ## Usage 5 | 6 | Before querying the handles, create a buffer that can hold all process handles. On most machines, ~5MB should be fine. 7 | 8 | Then call the function `handle.QueryHandles(...)`. This function takes the following arguments: 9 | 10 | - `buf`: A buffer that can hold all process handles 11 | - `processFilter` _(optional)_: Only show process handles of process with this id 12 | - `handleTypes` _(optional)_: Only return handles of the specified types, e.g. `File`, `Event` or `Mutant` 13 | - `queryTimeout`: Some handles can not be queried and cause a freeze. This timeout will be used to prevent freezes 14 | 15 | The function returns a list of handles. 16 | 17 | ## Example 18 | The following example iterates over all handles of this process. 19 | 20 | ```golang 21 | // create an example global and local event 22 | eventHandle, err := createEvent(`Global\TestHandleEvent`) 23 | if err != nil { 24 | panic(err) 25 | } 26 | defer windows.CloseHandle(eventHandle) 27 | eventHandle2, err := createEvent(`Local\TestHandleEvent2`) 28 | if err != nil { 29 | panic(err) 30 | } 31 | defer windows.CloseHandle(eventHandle2) 32 | // create an example global and local mutex 33 | mutexHandle, err := createMutex(`Global\TestHandleMutex`) 34 | if err != nil { 35 | panic(err) 36 | } 37 | defer windows.CloseHandle(mutexHandle) 38 | mutexHandle2, err := createMutex(`Local\TestHandleMutex2`) 39 | if err != nil { 40 | panic(err) 41 | } 42 | defer windows.CloseHandle(mutexHandle2) 43 | // create an example file 44 | f, err := os.OpenFile("TestFile", os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0600) 45 | if err != nil { 46 | panic(err) 47 | } 48 | defer os.Remove("TestFile") 49 | defer f.Close() 50 | // create 6MB buffer 51 | buf := make([]byte, 6000000) 52 | pid := uint16(os.Getpid()) 53 | handles, err := handle.QueryHandles(buf, &pid, []string{"File", "Event", "Mutant"}, time.Second*20) 54 | if err != nil { 55 | panic(err) 56 | } 57 | for _, fh := range handles { 58 | fmt.Printf("PID %05d | %8.8s | HANDLE %04X | '%s'\n", fh.Process, fh.Type, fh.Handle, fh.Name) 59 | } 60 | ``` 61 | 62 | `Z:\go\src\github.com\Codehardt\go-handle>go run examples/example.go` 63 | ``` 64 | PID 08108 | File | HANDLE 0004 | '\Device\ConDrv' 65 | PID 08108 | Event | HANDLE 0008 | '' 66 | PID 08108 | Event | HANDLE 000C | '' 67 | PID 08108 | Event | HANDLE 003C | '' 68 | PID 08108 | Event | HANDLE 0040 | '' 69 | PID 08108 | File | HANDLE 0044 | '\Device\Mup\VBoxSvr\win10\' 70 | PID 08108 | File | HANDLE 0048 | '\Device\ConDrv' 71 | PID 08108 | File | HANDLE 00A0 | '\Device\DeviceApi' 72 | PID 08108 | File | HANDLE 00AC | '\Device\KsecDD' 73 | PID 08108 | File | HANDLE 00B0 | '\Device\CNG' 74 | PID 08108 | Event | HANDLE 00B4 | '' 75 | PID 08108 | Event | HANDLE 00C4 | '' 76 | PID 08108 | Event | HANDLE 00DC | '' 77 | PID 08108 | Event | HANDLE 00E8 | '' 78 | PID 08108 | Event | HANDLE 00F0 | '' 79 | PID 08108 | Event | HANDLE 00FC | '' 80 | PID 08108 | Event | HANDLE 0104 | '' 81 | PID 08108 | Event | HANDLE 0110 | '\BaseNamedObjects\TestHandleEvent' 82 | PID 08108 | Event | HANDLE 0114 | '\Sessions\1\BaseNamedObjects\TestHandleEvent2' 83 | PID 08108 | Mutant | HANDLE 0118 | '\BaseNamedObjects\TestHandleMutex' 84 | PID 08108 | Mutant | HANDLE 011C | '\Sessions\1\BaseNamedObjects\TestHandleMutex2' 85 | PID 08108 | File | HANDLE 0120 | '\Device\Mup\VBoxSvr\win10\TestFile' 86 | PID 08108 | Event | HANDLE 0124 | '' 87 | PID 08108 | File | HANDLE 018C | '\Device\ConDrv' 88 | PID 08108 | File | HANDLE 01BC | '\Device\ConDrv' 89 | PID 08108 | File | HANDLE 01DC | '\Device\ConDrv' 90 | ``` -------------------------------------------------------------------------------- /examples/example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "time" 7 | 8 | "github.com/NextronSystems/go-handle" 9 | "golang.org/x/sys/windows" 10 | ) 11 | 12 | func createEvent(name string) (windows.Handle, error) { 13 | u16, err := windows.UTF16PtrFromString(name) 14 | if err != nil { 15 | return 0, err 16 | } 17 | h, err := windows.CreateEvent(nil, 0, 0, u16) 18 | if err != nil { 19 | return 0, err 20 | } 21 | return h, nil 22 | } 23 | 24 | func createMutex(name string) (windows.Handle, error) { 25 | u16, err := windows.UTF16PtrFromString(name) 26 | if err != nil { 27 | return 0, err 28 | } 29 | h, err := windows.CreateMutex(nil, false, u16) 30 | if err != nil { 31 | return 0, err 32 | } 33 | return h, nil 34 | } 35 | 36 | func main() { 37 | // create an example global and local event 38 | eventHandle, err := createEvent(`Global\TestHandleEvent`) 39 | if err != nil { 40 | panic(err) 41 | } 42 | defer windows.CloseHandle(eventHandle) 43 | eventHandle2, err := createEvent(`Local\TestHandleEvent2`) 44 | if err != nil { 45 | panic(err) 46 | } 47 | defer windows.CloseHandle(eventHandle2) 48 | // create an example global and local mutex 49 | mutexHandle, err := createMutex(`Global\TestHandleMutex`) 50 | if err != nil { 51 | panic(err) 52 | } 53 | defer windows.CloseHandle(mutexHandle) 54 | mutexHandle2, err := createMutex(`Local\TestHandleMutex2`) 55 | if err != nil { 56 | panic(err) 57 | } 58 | defer windows.CloseHandle(mutexHandle2) 59 | // create an example file 60 | f, err := os.OpenFile("TestFile", os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0600) 61 | if err != nil { 62 | panic(err) 63 | } 64 | defer os.Remove("TestFile") 65 | defer f.Close() 66 | // create 6MB buffer 67 | buf := make([]byte, 6000000) 68 | pid := uint32(os.Getpid()) 69 | handles, err := handle.QueryHandles(buf, &pid, nil, time.Millisecond*500) 70 | if err != nil { 71 | panic(err) 72 | } 73 | for _, fh := range handles { 74 | fmt.Printf("PID %05d | %8.8s | HANDLE %04X | '%s'\n", fh.Process, fh.Type, fh.Handle, fh.Name) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /handle.go: -------------------------------------------------------------------------------- 1 | //+build windows 2 | 3 | package handle 4 | 5 | import ( 6 | "fmt" 7 | "io" 8 | "runtime" 9 | "time" 10 | ) 11 | 12 | type Handle struct { 13 | Process uint32 14 | Handle uint3264 15 | Name string 16 | Type string 17 | } 18 | 19 | func QueryHandles(buf []byte, processFilter *uint32, handleTypes []string, queryTimeout time.Duration) (handles []Handle, err error) { 20 | systemHandles, err := NtQuerySystemHandles(buf) 21 | if err != nil { 22 | return nil, err 23 | } 24 | var typeFilter map[string]struct{} 25 | if len(handleTypes) > 0 { 26 | typeFilter = make(map[string]struct{}) 27 | for _, handleType := range handleTypes { 28 | typeFilter[handleType] = struct{}{} 29 | } 30 | } 31 | log("type filter: %#v", typeFilter) 32 | log("handle count: %d", len(systemHandles)) 33 | inspector := NewInspector(queryTimeout) 34 | defer inspector.Close() 35 | for _, handle := range systemHandles { 36 | log("handle: %#v", handle) 37 | if processFilter != nil && *processFilter != uint32(handle.UniqueProcessID) { 38 | log("skipping handle of process %d due to process filter %d", handle.UniqueProcessID, processFilter) 39 | continue 40 | } 41 | // some handles can cause a permanent wait within NtQueryObject. 42 | // While we handle those freezes (by killing the thread after a known timeout), 43 | // doing so causes memory to leak - this is apparently inherent to TerminateThread. 44 | // Therefore, we try to avoid blocking handles in general by blacklisting access masks 45 | // that might indicate this issue. 46 | if (handle.GrantedAccess == 0x0012019f) || 47 | (handle.GrantedAccess == 0x001a019f) || 48 | (handle.GrantedAccess == 0x00120189) || 49 | (handle.GrantedAccess == 0x00100000) { 50 | log("skipping handle due to granted access") 51 | continue 52 | } 53 | 54 | handleType, err := inspector.LookupHandleType(handle) 55 | if err != nil { 56 | log("could not query handle type for handle %d in process %d with access mask %d, error: %v", handle.HandleValue, handle.UniqueProcessID, handle.GrantedAccess, err) 57 | continue 58 | } 59 | if typeFilter != nil { 60 | if _, isTargetType := typeFilter[handleType]; !isTargetType { 61 | continue 62 | } 63 | } 64 | name, err := inspector.LookupHandleName(handle) 65 | if err != nil { 66 | log("could not query handle name for handle %d in process %d with access mask %d, error: %v", handle.HandleValue, handle.UniqueProcessID, handle.GrantedAccess, err) 67 | continue 68 | } 69 | handles = append(handles, Handle{ 70 | Process: uint32(handle.UniqueProcessID), 71 | Handle: handle.HandleValue, 72 | Name: name, 73 | Type: handleType, 74 | }) 75 | } 76 | runtime.KeepAlive(buf) 77 | return handles, nil 78 | } 79 | 80 | var writer io.Writer 81 | 82 | // DebugWriter sets a debug writer for debug logging, e.g. os.Stdout 83 | func DebugWriter(w io.Writer) { 84 | writer = w 85 | } 86 | 87 | func log(format string, a ...interface{}) { 88 | if writer == nil { 89 | return 90 | } 91 | fmt.Fprintf(writer, format+"\n", a...) 92 | } 93 | -------------------------------------------------------------------------------- /handle_386.go: -------------------------------------------------------------------------------- 1 | //+build !amd64 2 | 3 | package handle 4 | 5 | import "unsafe" 6 | 7 | type uint3264 uint32 8 | 9 | const maxHandleCount = (1 << 31) / unsafe.Sizeof(SystemHandle{}) 10 | -------------------------------------------------------------------------------- /handle_amd64.go: -------------------------------------------------------------------------------- 1 | //+build amd64 2 | 3 | package handle 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | type uint3264 uint64 10 | 11 | const maxHandleCount = (1 << 50) / unsafe.Sizeof(SystemHandle{}) 12 | -------------------------------------------------------------------------------- /inspector.go: -------------------------------------------------------------------------------- 1 | package handle 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "time" 8 | 9 | "golang.org/x/sys/windows" 10 | ) 11 | 12 | // #include "queryobject.h" 13 | import "C" 14 | 15 | // Inspector describes a structure that queries details (name and type name) to a specific handle. 16 | // Common elements such as type ID to name mappings and process handles are cached and reused. 17 | type Inspector struct { 18 | nativeExchange *C.exchange_t 19 | typeMapping map[uint16]string 20 | processHandles map[uint32]windows.Handle 21 | timeout time.Duration 22 | ntQueryThread nativeThread 23 | } 24 | 25 | func NewInspector(timeout time.Duration) *Inspector { 26 | query := &Inspector{ 27 | typeMapping: map[uint16]string{}, 28 | processHandles: map[uint32]windows.Handle{}, 29 | timeout: timeout, 30 | nativeExchange: &C.exchange_t{}, 31 | } 32 | ini, _ := windows.CreateEvent(nil, 0, 0, nil) 33 | query.nativeExchange.ini = C.uintptr_t(ini) 34 | done, _ := windows.CreateEvent(nil, 0, 0, nil) 35 | query.nativeExchange.done = C.uintptr_t(done) 36 | 37 | return query 38 | } 39 | 40 | // Close the Inspector object, removing any cached data and stopping the native thread 41 | func (i *Inspector) Close() { 42 | if !i.ntQueryThread.IsZero() { 43 | i.ntQueryThread.Terminate() 44 | i.ntQueryThread = nativeThread{} 45 | } 46 | windows.CloseHandle(windows.Handle(i.nativeExchange.ini)) 47 | i.nativeExchange.ini = 0 48 | windows.CloseHandle(windows.Handle(i.nativeExchange.done)) 49 | i.nativeExchange.done = 0 50 | for _, handle := range i.processHandles { 51 | if handle != 0 { 52 | windows.CloseHandle(handle) 53 | } 54 | } 55 | i.processHandles = map[uint32]windows.Handle{} 56 | } 57 | 58 | var ownpid = uint3264(os.Getpid()) 59 | 60 | // LookupHandleType returns the type name for the handle. If possible, a cached type 61 | // is used; otherwise, the handle is duplicated and its type is looked up. 62 | func (i *Inspector) LookupHandleType(handle SystemHandle) (handleType string, err error) { 63 | handleType, knownType := i.typeMapping[handle.ObjectTypeIndex] 64 | if knownType { 65 | return handleType, nil 66 | } 67 | var h windows.Handle 68 | // duplicate handle if it's not from our own process 69 | if handle.UniqueProcessID != ownpid { 70 | h, err = i.duplicateHandle(handle) 71 | if err != nil { 72 | return "", fmt.Errorf("could not duplicate handle: %w", err) 73 | } 74 | defer windows.CloseHandle(h) 75 | } else { 76 | h = windows.Handle(handle.HandleValue) 77 | } 78 | handleType, err = i.ntQueryObject(h, typeInformationClass) 79 | i.typeMapping[handle.ObjectTypeIndex] = handleType 80 | if err != nil { 81 | return "", fmt.Errorf("could not query handle type: %w", err) 82 | } 83 | return 84 | } 85 | 86 | func (i *Inspector) LookupHandleName(handle SystemHandle) (name string, err error) { 87 | var h windows.Handle 88 | // duplicate handle if it's not from our own process 89 | if handle.UniqueProcessID != ownpid { 90 | h, err = i.duplicateHandle(handle) 91 | if err != nil { 92 | return "", fmt.Errorf("could not duplicate handle: %w", err) 93 | } 94 | defer windows.CloseHandle(h) 95 | } else { 96 | h = windows.Handle(handle.HandleValue) 97 | } 98 | name, err = i.ntQueryObject(h, nameInformationClass) 99 | return 100 | } 101 | 102 | // duplicateHandle duplicates a handle into our own process. It uses a cache for 103 | // process handles that are used repeatedly. 104 | func (i *Inspector) duplicateHandle(handle SystemHandle) (windows.Handle, error) { 105 | pid := uint32(handle.UniqueProcessID) 106 | p, hasCachedHandle := i.processHandles[pid] 107 | if !hasCachedHandle { 108 | var err error 109 | p, err = windows.OpenProcess( 110 | windows.PROCESS_DUP_HANDLE, 111 | true, 112 | pid, 113 | ) 114 | i.processHandles[pid] = p 115 | if err != nil { 116 | return 0, err 117 | } 118 | } else if p == 0 { // Error was cached 119 | return 0, errors.New("failed to open process") 120 | } 121 | var h windows.Handle 122 | if err := windows.DuplicateHandle( 123 | p, 124 | windows.Handle(handle.HandleValue), 125 | windows.CurrentProcess(), 126 | &h, 127 | 0, 128 | false, 129 | windows.DUPLICATE_SAME_ACCESS, 130 | ); err != nil { 131 | return 0, err 132 | } 133 | return h, nil 134 | } 135 | 136 | var ErrTimeout = errors.New("NtQueryObject deadlocked") 137 | -------------------------------------------------------------------------------- /nativethread.go: -------------------------------------------------------------------------------- 1 | package handle 2 | 3 | import ( 4 | "golang.org/x/sys/windows" 5 | ) 6 | 7 | var ( 8 | kernel32 = windows.NewLazyDLL("kernel32.dll") 9 | 10 | createThread = kernel32.NewProc("CreateThread") 11 | terminateThread = kernel32.NewProc("TerminateThread") 12 | ) 13 | 14 | type nativeThread struct { 15 | handle windows.Handle 16 | } 17 | 18 | func createNativeThread(callback uintptr, param uintptr) (nativeThread, error) { 19 | var thread nativeThread 20 | h, _, err := createThread.Call( 21 | 0, 22 | 0, 23 | callback, 24 | param, 25 | 0, 26 | 0, 27 | ) 28 | if h == 0 { 29 | return nativeThread{}, err 30 | } 31 | thread.handle = windows.Handle(h) 32 | return thread, nil 33 | } 34 | 35 | func (t nativeThread) Terminate() error { 36 | r1, _, err := terminateThread.Call( 37 | uintptr(t.handle), 38 | 0, 39 | ) 40 | windows.CloseHandle(t.handle) 41 | if r1 == 0 { 42 | return err 43 | } 44 | return nil 45 | } 46 | 47 | func (t nativeThread) IsZero() bool { 48 | return t.handle == 0 49 | } 50 | -------------------------------------------------------------------------------- /queryobject.c: -------------------------------------------------------------------------------- 1 | #include "queryobject.h" 2 | 3 | #include 4 | 5 | typedef NTSTATUS NTAPI (*NtQueryObjectType)(HANDLE Handle,OBJECT_INFORMATION_CLASS ObjectInformationClass,PVOID ObjectInformation,ULONG ObjectInformationLength,PULONG ReturnLength); 6 | 7 | int queryObjects(exchange_t* exchange) { 8 | HMODULE ntdll = LoadLibraryA("ntdll.dll"); 9 | NtQueryObjectType ntQueryObject = (NtQueryObjectType) GetProcAddress(ntdll, "NtQueryObject"); 10 | 11 | while(1) { 12 | if (WaitForSingleObject((HANDLE) exchange->ini, INFINITE) != WAIT_OBJECT_0) { 13 | return 1; 14 | } 15 | exchange->result = ntQueryObject((HANDLE) exchange->handle, exchange->informationClass, exchange->buffer, BUFFER_LENGTH, 0); 16 | SetEvent((HANDLE) exchange->done); 17 | } 18 | return 0; 19 | } -------------------------------------------------------------------------------- /queryobject.go: -------------------------------------------------------------------------------- 1 | package handle 2 | 3 | // #include "queryobject.h" 4 | import "C" 5 | import ( 6 | "errors" 7 | "time" 8 | "unsafe" 9 | 10 | "golang.org/x/sys/windows" 11 | ) 12 | 13 | type objectTypeInformation struct { 14 | TypeName windows.NTUnicodeString 15 | _ [22]uint64 // unused 16 | } 17 | 18 | type objectNameInformation struct { 19 | Name windows.NTUnicodeString 20 | } 21 | 22 | const ( 23 | nameInformationClass = iota + 1 24 | typeInformationClass 25 | ) 26 | 27 | // ntQueryObject wraps NtQueryObject and supports a timeout logic. 28 | // Because NtQueryObject can deadlock on specific handles, we do 29 | // not want to call it directly. We also can't call it in a separate 30 | // go routine because then that go routine might be permanently blocked. 31 | // 32 | // Instead, we use a native ntQueryThread that starts queryObjects and 33 | // communicate with it via a CGO struct. If the response pipe times 34 | // out, we assume a deadlock and kill the native ntQueryThread. 35 | func (i *Inspector) ntQueryObject(h windows.Handle, informationClass int) (string, error) { 36 | if i.ntQueryThread.IsZero() { 37 | if err := windows.ResetEvent(windows.Handle(i.nativeExchange.ini)); err != nil { 38 | return "", err 39 | } 40 | if err := windows.ResetEvent(windows.Handle(i.nativeExchange.done)); err != nil { 41 | return "", err 42 | } 43 | var err error 44 | i.ntQueryThread, err = createNativeThread(uintptr(C.queryObjects), uintptr(unsafe.Pointer(i.nativeExchange))) 45 | if err != nil { 46 | return "", err 47 | } 48 | } 49 | i.nativeExchange.handle = C.uintptr_t(h) 50 | i.nativeExchange.informationClass = C.int(informationClass) 51 | 52 | if err := windows.SetEvent(windows.Handle(i.nativeExchange.ini)); err != nil { 53 | return "", err 54 | } 55 | 56 | if s, err := windows.WaitForSingleObject(windows.Handle(i.nativeExchange.done), uint32(i.timeout/time.Millisecond)); s == uint32(windows.WAIT_TIMEOUT) || err != nil { 57 | i.ntQueryThread.Terminate() 58 | i.ntQueryThread = nativeThread{} 59 | return "", ErrTimeout 60 | } 61 | if i.nativeExchange.result != 0 { 62 | return "", windows.NTStatus(i.nativeExchange.result) 63 | } 64 | 65 | var str windows.NTUnicodeString 66 | if informationClass == nameInformationClass { 67 | str = (*objectNameInformation)(unsafe.Pointer(&i.nativeExchange.buffer)).Name 68 | } else if informationClass == typeInformationClass { 69 | str = (*objectTypeInformation)(unsafe.Pointer(&i.nativeExchange.buffer)).TypeName 70 | } else { 71 | panic(informationClass) 72 | } 73 | if str.Buffer == nil { 74 | return "", errors.New("NTQueryObject returned nil pointer") 75 | } 76 | return str.String(), nil 77 | } 78 | -------------------------------------------------------------------------------- /queryobject.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define BUFFER_LENGTH 1000 5 | 6 | typedef struct { 7 | // Ini is waited upon by the native thread and is triggered when a valid handle and information class have been placed in the input buffers 8 | uintptr_t ini; 9 | // Ini is triggered by the native thread when NtQueryObject is finished and the output buffer has been filled 10 | uintptr_t done; 11 | 12 | // Input data for NtQueryObject 13 | uintptr_t handle; 14 | int informationClass; 15 | // Output buffer for NtQueryObject 16 | byte buffer[BUFFER_LENGTH]; 17 | // NtQueryObject return value 18 | int result; 19 | } exchange_t; 20 | 21 | int queryObjects(exchange_t* exchange); -------------------------------------------------------------------------------- /querysystemhandles.go: -------------------------------------------------------------------------------- 1 | package handle 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "unsafe" 7 | 8 | "golang.org/x/sys/windows" 9 | ) 10 | 11 | // SystemHandle is the OS based definition of SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX 12 | type SystemHandle struct { 13 | Object uint3264 14 | UniqueProcessID uint3264 15 | HandleValue uint3264 16 | GrantedAccess uint32 17 | CreatorBackTraceIndex uint16 18 | ObjectTypeIndex uint16 19 | HandleAttributes uint32 20 | _ uint32 21 | } 22 | 23 | type systemHandleInformationEx struct { 24 | Count uint3264 25 | _ uint3264 26 | // ... followed by the specified number of handles 27 | Handles [maxHandleCount]SystemHandle 28 | } 29 | 30 | type InsufficientBufferError struct { 31 | RequiredBufferSize uint32 32 | } 33 | 34 | func (i InsufficientBufferError) Error() string { 35 | return fmt.Sprintf("a buffer of at least %d bytes is required", i.RequiredBufferSize) 36 | } 37 | 38 | func NtQuerySystemHandles(buf []byte) ([]SystemHandle, error) { 39 | // reset buffer, querying system information seem to require a 0-valued buffer. 40 | // Without this reset, the below sysinfo.Count might be wrong. 41 | for i := 0; i < len(buf); i++ { 42 | buf[i] = 0 43 | } 44 | // load all handle information to buffer and convert it to systemHandleInformation 45 | var requiredBuffer uint32 46 | if err := windows.NtQuerySystemInformation( 47 | 0x40, // SystemExtendedHandleInformation 48 | unsafe.Pointer(&buf[0]), 49 | uint32(len(buf)), 50 | &requiredBuffer, 51 | ); err != nil { 52 | if err == windows.STATUS_INFO_LENGTH_MISMATCH { 53 | return nil, InsufficientBufferError{requiredBuffer} 54 | } 55 | return nil, err 56 | } 57 | sysinfo := (*systemHandleInformationEx)(unsafe.Pointer(&buf[0])) 58 | if int(sysinfo.Count) > len(sysinfo.Handles) { 59 | return nil, errors.New("too many handles on system") 60 | } 61 | var handles = sysinfo.Handles[:sysinfo.Count:sysinfo.Count] 62 | return handles, nil 63 | } 64 | --------------------------------------------------------------------------------