potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。

C++20&Win API IMAGE_COR20_HEADERを取得する

概要

32ビット形式のPEファイルを生データとして解析してIMAGE_COR20_HEADER構造体を取得するサンプルコードのC++20版です。実際にはIMAGE_COR20_HEADER構造体のメインであろうメタデータは今後の拡張に備えてフォーマット非公開なのでCLSID_CorMetaDataDispenserで公開されるクラスを使うべきだと思います。

C++11版はこちらです

コード

#include <utility>
#include <format>
#include <span>
#include <memory>

#define STRICT
#include <Windows.h>
#include <CorHdr.h>

// PEファイルのRVAからRVAを含むセクションのIMAGE_SECTION_HEADERを返します。
const PIMAGE_SECTION_HEADER ImageSectionHeaderContainingRVA(
    LPCVOID Base,
    const IMAGE_NT_HEADERS32* NTHeaders,
    DWORD RVA) noexcept
{
    // IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTORを含むセクションの探索
    const auto p{ IMAGE_FIRST_SECTION(NTHeaders) };
    for (auto& SectionHeader : std::span{ p, p + NTHeaders->FileHeader.NumberOfSections })
    {
        if (SectionHeader.VirtualAddress <= RVA
            && RVA <= SectionHeader.VirtualAddress + SectionHeader.SizeOfRawData)
        {
            return &SectionHeader;
        }
    }
    return nullptr;
}

// PEファイルのRVAをVAに変換します。
LPCVOID ImageRVAToVA32(LPCVOID Base, const IMAGE_NT_HEADERS32* NTHeaders, DWORD RVA) noexcept
{
    auto SectionHeader{ ImageSectionHeaderContainingRVA(Base, NTHeaders, RVA) };
    if (SectionHeader == nullptr)
        return nullptr;
    return (std::bit_cast<LPCBYTE>(Base) + SectionHeader->PointerToRawData)
        + (RVA - SectionHeader->VirtualAddress);
}

// ファイルが32ビットPE形式であればIMAGE_NT_HEADERS32のポインタを返します。
const PIMAGE_NT_HEADERS32 GetPointerToImageNTHeaders32(LPCVOID Base) noexcept
{
    auto DOSHeader{ std::bit_cast<const PIMAGE_DOS_HEADER>(Base) };
    if (DOSHeader->e_magic != IMAGE_DOS_SIGNATURE)
        return nullptr;

    auto Signature{ std::bit_cast<const PDWORD>(std::bit_cast<LPCBYTE>(Base) + DOSHeader->e_lfanew) };
    if (*Signature != IMAGE_NT_SIGNATURE)
        return nullptr;

    auto FileHeader{ std::bit_cast<const PIMAGE_FILE_HEADER>(Signature + 1) };
    if (FileHeader->SizeOfOptionalHeader != sizeof IMAGE_OPTIONAL_HEADER32)
        return nullptr;

    auto OptionalHeader{ std::bit_cast<const PIMAGE_OPTIONAL_HEADER32>(FileHeader + 1) };
    if (OptionalHeader->Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC)
        return nullptr;

    return std::bit_cast<const PIMAGE_NT_HEADERS32>(Signature);
}

const PIMAGE_COR20_HEADER GetPointerToCorHeader20(LPCVOID Base) noexcept
{
    auto NTHeaders{ GetPointerToImageNTHeaders32(Base) };
    if (NTHeaders == nullptr)
        return nullptr;

    // IMAGE_COR2_HEADERはIMAGE_DIRECTORY_ENTRY_COM_DESCRIPTORに配置されます
    auto ComDescDir{ &NTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR] };
    return std::bit_cast<const PIMAGE_COR20_HEADER>(
        ImageRVAToVA32(Base, NTHeaders, ComDescDir->VirtualAddress));
}

#include <iostream>

struct HANDLE_deleter {
    void operator() (HANDLE handle) const noexcept {
        ::CloseHandle(handle);
    }
};
using unique_HANDLE = std::unique_ptr<std::remove_pointer_t<HANDLE>, HANDLE_deleter>;

struct MapViewOfFilePointer_deleter {
    void operator() (LPCVOID handle) const noexcept {
        ::UnmapViewOfFile(handle);
    }
};
using unique_MapViewOfFilePointer = std::unique_ptr<std::remove_pointer_t<HANDLE>, MapViewOfFilePointer_deleter>;

// 32ビットPEファイルのCLIヘッダー情報を表示します。
int main()
{
    std::wcout.imbue(std::locale("", std::locale::ctype));

    // CLIヘッダーを確認する実行ファイルの場所
    auto path{ <ファイル名を指定して下さい> };

    unique_HANDLE FileHandle{ CreateFileW(path, GENERIC_READ,
        FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr) };
    if (FileHandle.get() == INVALID_HANDLE_VALUE)
    {
        std::wcout << L"ファイルハンドルが開けませんでした。" << std::endl;
        return -1;
    }
    unique_HANDLE FileMapHandle{ CreateFileMappingW(FileHandle.get(), nullptr, PAGE_READONLY, 0, 0, nullptr) };
    if (FileMapHandle == nullptr)
    {
        std::wcout << L"ファイルマッピングオブジェクトが作成できませんでした。" << std::endl;
        return -1;
    }
    unique_MapViewOfFilePointer mapViewOfFile{ MapViewOfFile(FileMapHandle.get(), FILE_MAP_READ, 0, 0, 0) };
    auto Base{ std::bit_cast<LPCBYTE>(mapViewOfFile.get()) };
    if (Base == nullptr)
    {
        std::wcout << L"ファイルマッピングオブジェクトのビューが作成できませんでした。" << std::endl;
        return -1;
    }

    auto CorHeader{ GetPointerToCorHeader20(Base) };
    if (CorHeader == nullptr)
    {
        std::wcout << L"ファイルはNTヘッダーまたはCLIヘッダーを含みません。" << std::endl;
        return -1;
    }

    // CLIヘッダー情報の出力
    auto entryPointType{ (CorHeader->Flags & COMIMAGE_FLAGS_NATIVE_ENTRYPOINT) ? L"Native" : L"Managed" };
    std::wcout
        << std::format(
            L"IMAGE_COR20_HEADER\n"
            "ランタイムのバージョン: {}.{}\n"
            "メタデータのサイズ: {}\n"
            "フラグ: 0x{:08X}\n"
            "エントリーポイント: 0x{:08X} ({})",
            CorHeader->MajorRuntimeVersion, CorHeader->MinorRuntimeVersion,
            CorHeader->MetaData.Size,
            CorHeader->Flags,
            CorHeader->EntryPointRVA, entryPointType)
        << std::endl;

    return 0;
}