local function dump_table(label, table_base, limit, expected_module)
if limit <= 0 or limit > 0x4000 then
print(("%s: implausible limit=%d, aborting"):format(label, limit))
return
end
print(("%s: base=%s limit=%d"):format(label, tostring(table_base), limit))
local hooks = 0
for i = 0, limit - 1 do
local raw = ntos.read_dword(table_base + i * 4)
local spilled_args = raw & 0xf
if raw >= 0x80000000 then raw = raw - 0x100000000 end
local target = table_base + (raw // 16)
local sym = ntos.try_closest_symbol_any(target)
local module = sym and sym.module or nil
local display
if not sym then
display = tostring(target)
elseif sym.offset == 0 then
display = ("%s!%s"):format(module, sym.name)
else
display = ("%s!%s+0x%x"):format(module, sym.name, sym.offset)
end
local hooked = module and expected
and not module:lower():find(expected, 1, true)
local mark = hooked and " [HOOK]" or ""
if hooked then hooks = hooks + 1 end
local _ = spilled_args
print((" [%4d] %-50s%s"):format(i, display, mark))
end
if hooks > 0 then
print((" %d hook(s) detected"):format(hooks))
end
end
local function dump_shadow()
local sdt = ntos.try_eval("KeServiceDescriptorTableShadow")
if not sdt then
print("shadow SSDT: KeServiceDescriptorTableShadow unavailable in symbols")
return
end
local desc_size = ntos.try_type_size("_KSERVICE_TABLE_DESCRIPTOR") or 0x20
local base_off = ntos.try_offset_of("_KSERVICE_TABLE_DESCRIPTOR", "Base") or 0
local limit_off = ntos.try_offset_of("_KSERVICE_TABLE_DESCRIPTOR", "Limit") or 0x10
local win32k = sdt + desc_size
local base = ntos.read_qword(win32k + base_off)
if base == ntos.addr(0) then
print("shadow SSDT: win32k descriptor not initialized (no GUI threads yet?)")
return
end
local limit = ntos.read_dword(win32k + limit_off)
dump_table("shadow SSDT (win32k)", base, limit, "win32k")
end
register_command("ssdt", "Dump the SSDT and shadow SSDT.\n(usage: ssdt)", function()
dump_table("SSDT", ntos.eval("KiServiceTable"),
ntos.read_dword(ntos.eval("KiServiceLimit")), "nt")
print()
dump_shadow()
end)