potisanのプログラミングメモ

プログラミング素人です。昔の自分を育ててくれたネット情報に少しでも貢献できるよう、情報を貯めていこうと思っています。Windows環境のC++やC#がメインです。

C++&Win API NtQueryObject関数とObjectTypesInformation (3)でカーネルオブジェクトの型名を列挙

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

*1:Windowsには歴史的に非公開APIがあり、いくつかはユーザーや他企業により発見されて後付けで公式APIとなっています。NtQueryObjectもそのうちの一つですが、多機能だからか一部機能のみ公開されています。