mirror of
https://github.com/NomanNasirMinhas/Ringer
synced 2026-08-19 05:01:13 +00:00
195 lines
5.3 KiB
Go
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)
|
|
}
|