Files
NomanNasirMinhas 28f02e2fd2 init
2026-08-16 19:28:23 +03:00

195 lines
5.3 KiB
Go

// Package verify connects to a live, already-loaded driver and exercises the
// IOCTL codes and shared-memory sections extracted statically.
package verify
import (
"fmt"
"strings"
"time"
"golang.org/x/sys/windows"
"ringer/extract"
)
// IoctlResult is the outcome of a single DeviceIoControl call.
type IoctlResult struct {
Code uint32
Device string
Success bool
ErrorCode uint32
Error string
BytesReturned uint32
Output []byte
TimedOut bool
}
// DeviceCandidates derives candidate user-mode device paths from extracted
// strings. Kernel device names (\Device\X) and symbolic links (\DosDevices\X,
// \??\X) are both tried, in the local and global object namespaces.
func DeviceCandidates(strs []extract.StringInfo) []string {
seen := map[string]bool{}
var out []string
add := func(p string) {
if p == "" || seen[p] {
return
}
seen[p] = true
out = append(out, p)
}
for _, s := range strs {
switch s.Category {
case "device":
name := strings.TrimPrefix(s.Value, `\Device\`)
add(`\\.\` + name)
add(`\\.\Global\` + name)
case "symlink":
name := strings.TrimPrefix(s.Value, `\DosDevices\`)
name = strings.TrimPrefix(name, `\??\`)
add(`\\.\` + name)
}
}
return out
}
// VerifyIoctls opens each candidate device and sends every IOCTL code, reading
// back the output buffer. It returns one result per (device, code) pair.
func VerifyIoctls(devices []string, codes []extract.IoctlCode, bufferSize int, timeout time.Duration) []IoctlResult {
if bufferSize <= 0 {
bufferSize = 4096
}
if timeout <= 0 {
timeout = 2 * time.Second
}
var results []IoctlResult
for _, dev := range devices {
h, err := openDevice(dev)
if err != nil {
// Record a single failure for the device so the caller knows it
// could not be opened, then skip its IOCTLs.
results = append(results, IoctlResult{
Device: dev,
ErrorCode: uint32(err.(windows.Errno)),
Error: err.Error(),
})
continue
}
for _, c := range codes {
res := probeIoctl(h, dev, c.Code, bufferSize, timeout)
results = append(results, res)
}
windows.CloseHandle(h)
}
return results
}
// openDevice opens a device path with overlapped I/O so probes can be timed out.
func openDevice(path string) (windows.Handle, error) {
p, err := windows.UTF16PtrFromString(path)
if err != nil {
return 0, err
}
return windows.CreateFile(
p,
windows.GENERIC_READ|windows.GENERIC_WRITE,
windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE,
nil,
windows.OPEN_EXISTING,
windows.FILE_ATTRIBUTE_NORMAL|windows.FILE_FLAG_OVERLAPPED,
0,
)
}
// probeIoctl sends one IOCTL with a zeroed input buffer and reads the output.
// On ERROR_INSUFFICIENT_BUFFER it retries with a larger output buffer.
func probeIoctl(h windows.Handle, dev string, code uint32, bufferSize int, timeout time.Duration) IoctlResult {
res := IoctlResult{Code: code, Device: dev}
// METHOD_NEITHER dereferences user pointers directly in the kernel; a bad
// pointer can bugcheck. We still send it (per "always attempt live") but
// with real, valid buffers.
in := make([]byte, bufferSize)
out := make([]byte, bufferSize)
returned, err, timedOut := deviceIoControl(h, code, in, out, timeout)
res.TimedOut = timedOut
if timedOut {
res.Error = "timed out"
return res
}
// Retry with a larger output buffer on insufficient-buffer errors.
if err == windows.ERROR_INSUFFICIENT_BUFFER || err == windows.ERROR_MORE_DATA {
big := make([]byte, bufferSize*16)
returned, err, timedOut = deviceIoControl(h, code, in, big, timeout)
if timedOut {
res.TimedOut = true
res.Error = "timed out"
return res
}
out = big
}
res.BytesReturned = returned
if err != nil {
res.ErrorCode = uint32(err.(windows.Errno))
res.Error = win32ErrorName(res.ErrorCode)
return res
}
res.Success = true
res.Output = out[:returned]
return res
}
// deviceIoControl performs an overlapped DeviceIoControl with a timeout.
func deviceIoControl(h windows.Handle, code uint32, in, out []byte, timeout time.Duration) (returned uint32, err error, timedOut bool) {
ev, eerr := windows.CreateEvent(nil, 1, 0, nil)
if eerr != nil {
return 0, eerr, false
}
defer windows.CloseHandle(ev)
ov := &windows.Overlapped{HEvent: ev}
var inPtr, outPtr *byte
if len(in) > 0 {
inPtr = &in[0]
}
if len(out) > 0 {
outPtr = &out[0]
}
err = windows.DeviceIoControl(h, code, inPtr, uint32(len(in)), outPtr, uint32(len(out)), &returned, ov)
if err == windows.ERROR_IO_PENDING {
wait, _ := windows.WaitForSingleObject(ev, uint32(timeout.Milliseconds()))
if wait == uint32(windows.WAIT_TIMEOUT) {
windows.CancelIoEx(h, ov)
return 0, nil, true
}
err = windows.GetOverlappedResult(h, ov, &returned, false)
}
return returned, err, false
}
// win32ErrorName maps common Win32 error codes to friendly names.
func win32ErrorName(code uint32) string {
names := map[uint32]string{
0: "ERROR_SUCCESS",
1: "ERROR_INVALID_FUNCTION",
2: "ERROR_FILE_NOT_FOUND",
3: "ERROR_PATH_NOT_FOUND",
5: "ERROR_ACCESS_DENIED",
6: "ERROR_INVALID_HANDLE",
8: "ERROR_NOT_ENOUGH_MEMORY",
50: "ERROR_NOT_SUPPORTED",
87: "ERROR_INVALID_PARAMETER",
122: "ERROR_INSUFFICIENT_BUFFER",
234: "ERROR_MORE_DATA",
997: "ERROR_IO_PENDING",
1117: "ERROR_IO_DEVICE",
}
if n, ok := names[code]; ok {
return n
}
return fmt.Sprintf("Win32 error %d", code)
}