potisanのプログラミングメモ

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

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;
}