Files
NomanNasirMinhas-Ringer/runtime/object.go
T
NomanNasirMinhas 28f02e2fd2 init
2026-08-16 19:28:23 +03:00

236 lines
6.6 KiB
Go

package runtime
import (
"fmt"
"strings"
"unsafe"
"golang.org/x/sys/windows"
)
// Object manager directory enumeration. NtOpenDirectoryObject and
// NtQueryDirectoryObject are undocumented NT APIs, so they are called directly
// through ntdll.dll (x/sys/windows has no wrappers for them).
var (
modntdll = windows.NewLazySystemDLL("ntdll.dll")
procNtOpenDirectoryObject = modntdll.NewProc("NtOpenDirectoryObject")
procNtQueryDirectoryObject = modntdll.NewProc("NtQueryDirectoryObject")
)
// DIRECTORY_QUERY is the access right needed to enumerate a directory object.
const directoryQuery = 0x0001
// unicodeString mirrors the kernel UNICODE_STRING structure.
type unicodeString struct {
Length uint16
MaximumLength uint16
Buffer *uint16
}
// objectAttributes mirrors OBJECT_ATTRIBUTES.
type objectAttributes struct {
Length uint32
RootDirectory windows.Handle
ObjectName *unicodeString
Attributes uint32
SecurityDescriptor unsafe.Pointer
SecurityQualityOfService unsafe.Pointer
}
// objectDirectoryInformation mirrors OBJECT_DIRECTORY_INFORMATION, the entry
// type returned by NtQueryDirectoryObject.
type objectDirectoryInformation struct {
Name unicodeString
TypeName unicodeString
}
// ntOpenDirectoryObject opens an object manager directory by name.
func ntOpenDirectoryObject(handle *windows.Handle, access uint32, attrs *objectAttributes) error {
r0, _, _ := procNtOpenDirectoryObject.Call(
uintptr(unsafe.Pointer(handle)),
uintptr(access),
uintptr(unsafe.Pointer(attrs)),
)
if r0 != 0 {
return windows.NTStatus(r0)
}
return nil
}
// ntQueryDirectoryObject enumerates a directory object into buf, returning the
// number of bytes written in retLen.
func ntQueryDirectoryObject(handle windows.Handle, buf *byte, length uint32, restart bool, context *uint32, retLen *uint32) error {
var restartFlag uintptr
if restart {
restartFlag = 1
}
r0, _, _ := procNtQueryDirectoryObject.Call(
uintptr(handle),
uintptr(unsafe.Pointer(buf)),
uintptr(length),
0, // ReturnSingleEntry = false
restartFlag,
uintptr(unsafe.Pointer(context)),
uintptr(unsafe.Pointer(retLen)),
)
if r0 != 0 {
return windows.NTStatus(r0)
}
return nil
}
// EnumerateDirectory returns the names of all objects in an object manager
// directory (e.g. `\Device`, `\BaseNamedObjects`, `\GLOBAL??`).
func EnumerateDirectory(dir string) ([]string, error) {
name, err := windows.UTF16PtrFromString(dir)
if err != nil {
return nil, err
}
us := &unicodeString{
Length: uint16(len(dir) * 2),
MaximumLength: uint16((len(dir) + 1) * 2),
Buffer: name,
}
attrs := &objectAttributes{
Length: uint32(unsafe.Sizeof(objectAttributes{})),
ObjectName: us,
}
var h windows.Handle
if err := ntOpenDirectoryObject(&h, directoryQuery, attrs); err != nil {
return nil, err
}
defer windows.CloseHandle(h)
// Enumerate in passes. A directory can hold more entries than fit in one
// buffer, in which case NtQueryDirectoryObject returns STATUS_MORE_ENTRIES
// (or STATUS_BUFFER_TOO_SMALL for a single oversized entry). Grow the buffer
// and restart the scan from the beginning in that case.
bufSize := 64 * 1024
for {
buf := make([]byte, bufSize)
var out []string
var context uint32
restart := true
needGrow := false
for {
var retLen uint32
err := ntQueryDirectoryObject(h, &buf[0], uint32(len(buf)), restart, &context, &retLen)
if err == windows.STATUS_NO_MORE_ENTRIES {
break
}
if err == windows.STATUS_MORE_ENTRIES || err == windows.STATUS_BUFFER_TOO_SMALL {
needGrow = true
break
}
if err != nil {
return nil, err
}
out = append(out, parseDirectoryEntries(buf[:retLen])...)
restart = false
}
if !needGrow {
return out, nil
}
if bufSize >= 16*1024*1024 {
return nil, fmt.Errorf("directory %q too large to enumerate", dir)
}
bufSize *= 2
}
}
// parseDirectoryEntries decodes a buffer of OBJECT_DIRECTORY_INFORMATION
// entries. The fixed structures are packed at the start of the buffer; the Name
// and TypeName string data follows them, and each entry's Name.Buffer /
// TypeName.Buffer is a real pointer into that trailing region (not inline data).
func parseDirectoryEntries(buf []byte) []string {
base := uintptr(unsafe.Pointer(&buf[0]))
entrySize := int(unsafe.Sizeof(objectDirectoryInformation{}))
if len(buf) < entrySize {
return nil
}
// The first entry's Name.Buffer points at the start of the string-data
// region, which begins immediately after the packed structures. That gives
// the entry count without scanning into the string bytes.
first := (*objectDirectoryInformation)(unsafe.Pointer(&buf[0]))
stringStart := uintptr(unsafe.Pointer(first.Name.Buffer))
if stringStart < base {
return nil
}
numEntries := int(stringStart-base) / entrySize
var out []string
for i := 0; i < numEntries; i++ {
info := (*objectDirectoryInformation)(unsafe.Pointer(&buf[i*entrySize]))
nameLen := int(info.Name.Length)
if nameLen == 0 {
continue
}
namePtr := uintptr(unsafe.Pointer(info.Name.Buffer))
if namePtr < base || namePtr+uintptr(nameLen) > base+uintptr(len(buf)) {
continue
}
rel := namePtr - base
out = append(out, utf16BytesToString(buf[rel:rel+uintptr(nameLen)]))
}
return out
}
// utf16BytesToString converts a UTF-16LE byte slice to a Go string.
func utf16BytesToString(b []byte) string {
u := make([]uint16, len(b)/2)
for i := range u {
u[i] = uint16(b[i*2]) | uint16(b[i*2+1])<<8
}
return windows.UTF16ToString(u)
}
// DeviceNames returns the user-mode device paths (`\\.\X`) for every device and
// symbolic link visible in the object manager. It enumerates `\GLOBAL??` (the
// DosDevices namespace) and `\Device`.
func DeviceNames() []string {
seen := map[string]bool{}
var out []string
add := func(p string) {
if p == "" || seen[p] {
return
}
seen[p] = true
out = append(out, p)
}
// \GLOBAL?? holds the symbolic links (e.g. RTCore64 -> \Device\RTCore64).
if names, err := EnumerateDirectory(`\GLOBAL??`); err == nil {
for _, n := range names {
add(`\\.\` + n)
}
}
// \Device holds the device objects themselves.
if names, err := EnumerateDirectory(`\Device`); err == nil {
for _, n := range names {
add(`\\.\` + n)
}
}
return out
}
// SectionNames returns the names of named section objects in \BaseNamedObjects,
// mapped to the user-mode Global\ namespace.
func SectionNames() []string {
names, err := EnumerateDirectory(`\BaseNamedObjects`)
if err != nil {
return nil
}
var out []string
for _, n := range names {
if strings.HasPrefix(n, "Global\\") {
out = append(out, n)
} else {
out = append(out, `Global\`+n)
}
}
return out
}