ntoseye 0.12.0

Windows kernel debugger for Linux hosts running Windows under KVM/QEMU
-- Inspect a DRIVER_OBJECT and its device chain

local PTR_SIZE = 8
local MAJOR_FUNCTION_OFFSET = ntos.offset_of("_DRIVER_OBJECT", "MajorFunction")

local IRP_MJ = {
    [0x00] = "CREATE",
    [0x01] = "CREATE_NAMED_PIPE",
    [0x02] = "CLOSE",
    [0x03] = "READ",
    [0x04] = "WRITE",
    [0x05] = "QUERY_INFORMATION",
    [0x06] = "SET_INFORMATION",
    [0x07] = "QUERY_EA",
    [0x08] = "SET_EA",
    [0x09] = "FLUSH_BUFFERS",
    [0x0a] = "QUERY_VOLUME_INFORMATION",
    [0x0b] = "SET_VOLUME_INFORMATION",
    [0x0c] = "DIRECTORY_CONTROL",
    [0x0d] = "FILE_SYSTEM_CONTROL",
    [0x0e] = "DEVICE_CONTROL",
    [0x0f] = "INTERNAL_DEVICE_CONTROL",
    [0x10] = "SHUTDOWN",
    [0x11] = "LOCK_CONTROL",
    [0x12] = "CLEANUP",
    [0x13] = "CREATE_MAILSLOT",
    [0x14] = "QUERY_SECURITY",
    [0x15] = "SET_SECURITY",
    [0x16] = "POWER",
    [0x17] = "SYSTEM_CONTROL",
    [0x18] = "DEVICE_CHANGE",
    [0x19] = "QUERY_QUOTA",
    [0x1a] = "SET_QUOTA",
    [0x1b] = "PNP",
}

local function is_driver_object(drv)
    return drv.Type == 4 and drv.Size and drv.Size >= MAJOR_FUNCTION_OFFSET + 28 * PTR_SIZE
end

local function read_driver_object(addr)
    local drv = ntos.try_read_struct("_DRIVER_OBJECT", addr)
    if drv and is_driver_object(drv) then return addr, drv, "direct" end

    local ptr = ntos.try_read_qword(addr)
    if ptr and ptr ~= ntos.addr(0) then
        drv = ntos.try_read_struct("_DRIVER_OBJECT", ptr)
        if drv and is_driver_object(drv) then return ptr, drv, "pointer" end
    end

    if drv then return nil, drv, "direct" end
    return nil, nil, nil
end

local function resolve_driver_input(expr)
    local value = ntos.try_eval(expr)
    if value then return value end

    local driver = ntos.try_find_driver_object(expr)
    if driver then return driver.object end

    return nil
end

local function print_dispatch_table(driver)
    local base = driver + MAJOR_FUNCTION_OFFSET
    print("  dispatch table:")
    for i = 0, 0x1b do
        local fn = ntos.read_qword(base + i * PTR_SIZE)
        local name = IRP_MJ[i] or ("0x%x"):format(i)
        print(("    IRP_MJ_%-28s %s"):format(name, ntos.format_symbol(fn)))
    end
end

local function print_devices(first)
    print("  devices:")
    if not first or first == ntos.addr(0) then
        print("    (none)")
        return
    end

    local seen = {}
    local cur = first
    for _ = 1, 128 do
        local key = tostring(cur)
        if seen[key] then
            print(("    %s (cycle)"):format(key))
            return
        end
        seen[key] = true

        local dev = ntos.try_read_struct("_DEVICE_OBJECT", cur)
        if not dev then
            print(("    %s (unreadable)"):format(tostring(cur)))
            return
        end

        local attached = dev.AttachedDevice or ntos.addr(0)
        local next_dev = dev.NextDevice or ntos.addr(0)
        print(("    %s type=0x%x flags=0x%x characteristics=0x%x attached=%s next=%s"):format(
            tostring(cur),
            dev.DeviceType or 0,
            dev.Flags or 0,
            dev.Characteristics or 0,
            tostring(attached),
            tostring(next_dev)))

        if next_dev == ntos.addr(0) then return end
        cur = next_dev
    end
    print("    (stopped after 128 devices)")
end

register_command("drvobj",
    "Inspect a DRIVER_OBJECT and its device chain\n" ..
    "(usage: drvobj <driver-object-expression-or-name>)",
    {"driver"},
    function(expr)
        if not expr then
            ntos.command_usage()
            return
        end

        local input = resolve_driver_input(expr)
        if not input then
            print(("unknown driver object expression or name: %s"):format(expr))
            return
        end
        local addr, drv, mode = read_driver_object(input)
        if not addr then
            print(("%s does not look like a DRIVER_OBJECT or pointer to one"):format(tostring(input)))
            if drv and drv.Type then print(("  Type: 0x%x (expected 0x4)"):format(drv.Type)) end
            if drv and drv.Size then print(("  Size: 0x%x"):format(drv.Size)) end
            return
        end
        local name = ntos.try_read_unicode_string(addr + ntos.offset_of("_DRIVER_OBJECT", "DriverName"))

        print(("driver object %s (%s)"):format(tostring(addr), mode))
        if name then print(("  name          : %s"):format(name)) end
        print(("  driver start  : %s"):format(tostring(drv.DriverStart)))
        print(("  driver size   : 0x%x"):format(drv.DriverSize or 0))
        print(("  driver section: %s"):format(tostring(drv.DriverSection)))
        print(("  driver unload : %s"):format(ntos.format_symbol(drv.DriverUnload or ntos.addr(0))))
        print_devices(drv.DeviceObject)
        print_dispatch_table(addr)
    end)