NtQueryObject
関数とObjectTypesInformation (3)
で詰まったのでその概要と動作するコードを記録します。
詰まった点と解決策
NtQueryObject
関数とObjectTypesInformation (3)
を使うとPUBLIC_OBJECT_TYPE_INFORMATION
の個数とその配列を取得できます(以下、PUBLIC_OBJECT_TYPES_INFORMATION
と書きます)。このPUBLIC_OBJECT_TYPES_INFORMATION
の扱いで詰まりました。
PUBLIC_OBJECT_TYPES_INFORMATION
内の配列はPUBLIC_OBJECT_TYPE_INFORMATION
と可変長文字列(UNICODE_STRING TypeName
の内容)、32ビットまたは64ビットパディングの繰り返しです。PUBLIC_OBJECT_TYPE_INFORMATION
の単純な配列ではなく、UNICODE_STRING
の中身はバッファの末尾ではなく途中に点々と確保される点に注意が必要です。
NtQueryObject
関数
NtQueryObject
関数は公式の非公開API*1で、Microsoft Docsに対応するページはありますが、後方互換性が無い可能性が表記されています。現時点でMicrosoft Docsには記載のないObjectTypesInformation=3
を与えことでカーネルオブジェクトの型や内部情報を取得できます。
動作するコード
カーネルオブジェクトの型名をstd::vector<std::wstring>
型で取得して標準出力に出力します。
#include <bit> #include <vector> #include <span> #include <string> #include <format> #include <ranges> #include <iostream> #define STRICT #define NOMINMAX #include <Windows.h> #include <winternl.h> #pragma comment(lib, "ntdll.lib") const OBJECT_INFORMATION_CLASS ObjectTypesInformation = (OBJECT_INFORMATION_CLASS)3; struct PUBLIC_OBJECT_TYPES_INFORMATION { ULONG_PTR NumberOfTypes; PUBLIC_OBJECT_TYPE_INFORMATION TypeInfos[1]; }; static std::vector<std::wstring> GetKernelObjectTypeNames() { // NtQueryObjectはnullptrと0を与えるとrequiredに8(64ビット環境), // 8以上のサイズ?を与えるとrequiredに実際に必要なバイト数を返す。 DWORD required; ::NtQueryObject(nullptr, ObjectTypesInformation, nullptr, 0, &required); std::vector<BYTE> buffer(required); ::NtQueryObject(nullptr, ObjectTypesInformation, buffer.data(), required, &required); buffer.resize(required); if (!NT_SUCCESS(::NtQueryObject(nullptr, ObjectTypesInformation, buffer.data(), required, nullptr))) throw std::exception(); // PUBLIC_OBJECT_TYPES_INFORMATIONは間にUNICODE_STRINGの指す可変長文字列を挟んで並ぶ。 auto pinfos = std::bit_cast<const PUBLIC_OBJECT_TYPES_INFORMATION*>(buffer.data()); std::vector<std::wstring> names; names.reserve(pinfos->NumberOfTypes); auto pinfo = pinfos->TypeInfos; for (size_t i = 0; i < pinfos->NumberOfTypes; i++) { names.emplace_back(pinfo->TypeName.Buffer, pinfo->TypeName.Length / sizeof(WCHAR)); // パディングに合わせる。 auto paddedLen = ((pinfo->TypeName.MaximumLength + sizeof(size_t) - 1) / sizeof(size_t)) * sizeof(size_t); // PUBLIC_OBJECT_TYPE_INFORMATION直後の可変長文字列を跳ばす。 pinfo = std::bit_cast<const PUBLIC_OBJECT_TYPE_INFORMATION*>(std::bit_cast<BYTE*>(pinfo + 1) + paddedLen); } return names; } int wmain() { for (const auto& [i, name] : GetKernelObjectTypeNames() | std::views::enumerate) { std::wcout << std::format(L"#{}: {}\n", i, name); } return 0; }
パディングの計算はより良い方法があるかもしれません。
出力例
#0: Type #1: Directory #2: SymbolicLink #3: Token #4: Job #5: Process #6: Thread #7: Partition #8: UserApcReserve #9: IoCompletionReserve #10: ActivityReference #11: ProcessStateChange #12: ThreadStateChange #13: CpuPartition #14: PsSiloContextPaged #15: PsSiloContextNonPaged #16: DebugObject #17: Event #18: Mutant #19: Callback #20: Semaphore #21: Timer #22: IRTimer #23: Profile #24: KeyedEvent #25: WindowStation #26: Desktop #27: Composition #28: RawInputManager #29: CoreMessaging #30: ActivationObject #31: TpWorkerFactory #32: Adapter #33: Controller #34: Device #35: Driver #36: IoCompletion #37: WaitCompletionPacket #38: File #39: IoRing #40: TmTm #41: TmTx #42: TmRm #43: TmEn #44: Section #45: Session #46: Key #47: RegistryTransaction #48: DmaAdapter #49: ALPC Port #50: EnergyTracker #51: PowerRequest #52: WmiGuid #53: EtwRegistration #54: EtwSessionDemuxEntry #55: EtwConsumer #56: CoverageSampler #57: PcwObject #58: FilterConnectionPort #59: FilterCommunicationPort #60: NdisCmState #61: DxgkSharedResource #62: DxgkSharedKeyedMutexObject #63: DxgkSharedSyncObject #64: DxgkSharedSwapChainObject #65: DxgkDisplayManagerObject #66: DxgkSharedProtectedSessionObject #67: DxgkSharedBundleObject #68: DxgkCompositionObject #69: VRegConfigurationContext