libpe Библиотека разбора бинарных файлов PE32/PE32+

Библиотека для анализа внутренних структур PE32/PE32+ бинарных файлов

Введение

libpe – это легкая и очень быстрая библиотека для разбора бинарных файлов PE32 (x86) и PE32+ (x64), реализованная в виде модуля C++20.

Содержание

Особенности

  • Работает с бинарными файлами PE32 (x86) и PE32+ (x64)
  • Получает все структуры данных PE32 / PE32+:
    • Заголовок MSDOS
    • «Rich» заголовок
    • NT / Файл / Опциональные заголовки
    • Таблицы данных
    • Разделы
    • Таблица экспорта
    • Таблица импорта
    • Таблица ресурсов
    • Таблица исключений
    • Таблица безопасности
    • Таблица перемещений
    • Таблица отладки
    • Таблица TLS
    • Каталог конфигурации загрузки
    • Таблица связанного импорта
    • Таблица отложенного импорта
    • Таблица COM

Pepper – одно из приложений, которое построено на основе libpe.

Использование

import libpe;

int main() {
    libpe::Clibpe pe(L"C:\\myFile.exe"); //или pe.OpenFile(L"C:\\myFile.exe");
    const auto peImp = pe.GetImport();
    if(peImp) {
    ...
    }
    ...
}

Методы

OpenFile

auto OpenFile(const wchar_t* pwszFile)->int;

Открывает файл для дальнейшей обработки, пока не будет вызвано CloseFile или объект Clibpe выйдет из области видимости и файл будет закрыт автоматически в деструкторе.

libpe::Clibpe pe;
if(pe.OpenFile(L"C:\\MyFile.exe") == PEOK) {
    ...
}

CloseFile();

void CloseFile();

Явно закрывает ранее открытый файл с помощью OpenFile(const wchar_t*). Этот метод вызывается автоматически в деструкторе Clibpe.

GetDOSHeader

[[nodiscard]] auto GetDOSHeader()const->std::optional<IMAGE_DOS_HEADER>;

Возвращает стандартный заголовок MS-DOS файла.

GetRichHeader

[[nodiscard]] auto GetRichHeader()const->std::optional<PERICHHDR_VEC>;

Возвращает массив так называемых неподтвержденных и неофициальных «Rich» структур.

struct PERICHHDR {
    DWORD dwOffset; //Смещение записи файла.
    WORD  wId;      //Идентификатор записи.
    WORD  wVersion; //Версия записи.
    DWORD dwCount;  //Количество вхождений.
};
using PERICHHDR_VEC = std::vector<PERICHHDR>;

GetNTHeader

[[nodiscard]] auto GetNTHeader()const->std::optional<PENTHDR>;

Возвращает заголовок NT файла.

struct PENTHDR {
    DWORD dwOffset;   //Смещение заголовка файла.
    union UNPENTHDR { //Объединение x86 или x64 NT заголовка.
        IMAGE_NT_HEADERS32 stNTHdr32; //Заголовок x86.
        IMAGE_NT_HEADERS64 stNTHdr64; //Заголовок x64.
    } unHdr;
};

GetDataDirs

[[nodiscard]] auto GetDataDirs()const->std::optional<PEDATADIR_VEC>;

Возвращает массив структур каталогов данных файла.

struct PEDATADIR {
    IMAGE_DATA_DIRECTORY stDataDir;  //Стандартный заголовок.
    std::string          strSection; //Имя секции, в которой находится этот каталог (указывает на нее).
};
using PEDATADIR_VEC = std::vector<PEDATADIR>;

GetSecHeaders

[[nodiscard]] auto GetSecHeaders()const->std::optional<PESECHDR_VEC>;

Возвращает массив структур заголовков секций файла.

struct PESECHDR {
    DWORD                dwOffset;   //Смещение дескриптора заголовков секций файла.
    IMAGE_SECTION_HEADER stSecHdr;   //Стандартный заголовок секции.
    std::string          strSecName; //Полное имя секции.
};
using PESECHDR_VEC = std::vector<PESECHDR>;

GetExport

[[nodiscard]] auto GetExport()const->std::optional<PEEXPORT>;

Возвращает информацию о экспорте файла.

struct PEEXPORTFUNC {
    DWORD       dwFuncRVA;        // RVA функции.
    DWORD       dwOrdinal;        // Ординал функции.
    DWORD       dwNameRVA;        // RVA имени функции.
    std::string strFuncName;      // Имя функции.
    std::string strForwarderName; // Имя переадресованной функции.
};
struct PEEXPORT {
    DWORD                     dwOffset;      // Смещение в файле для дескриптора заголовка экспорта.
    IMAGE_EXPORT_DIRECTORY    stExportDesc;  // Стандартный дескриптор заголовка экспорта.
    std::string               strModuleName; // Фактическое имя модуля.
    std::vector<PEEXPORTFUNC> vecFuncs;      // Массив структур экспортированных функций.
};

Пример:

libpe::Clibpe pe(L"ПУТЬ_К_ФАЙЛУ_PE");
const auto peExport = pe.GetExport();
if (!peExport) {
    return;
}

peExport->stExportDesc;  // Структура IMAGE_EXPORT_DIRECTORY.
peExport->strModuleName; // Имя экспортируемого модуля.
peExport->vecFuncs;      // Вектор экспортируемых функций.

for (const auto& itFuncs : peExport->vecFuncs) {
    itFuncs.strFuncName;      // Имя функции.
    itFuncs.dwOrdinal;        // Ординал.
    itFuncs.dwFuncRVA;        // RVA функции.
    itFuncs.strForwarderName; // Имя переадресованной функции.
}

GetImport

[[nodiscard]] auto GetImport()const->std::optional<PEIMPORT_VEC>;

Возвращает массив записей таблицы импорта файла.

struct PEIMPORTFUNC {
    union UNPEIMPORTTHUNK {
    	IMAGE_THUNK_DATA32 stThunk32; // Стандартная заглушка x86.
	    IMAGE_THUNK_DATA64 stThunk64; // Стандартная заглушка x64.
	} unThunk;
    IMAGE_IMPORT_BY_NAME stImpByName; // Структура стандартного IMAGE_IMPORT_BY_NAME.
    std::string          strFuncName; // Имя функции.
};
struct PEIMPORT {
    DWORD                     dwOffset;      // Смещение в файле для дескриптора импорта.
    IMAGE_IMPORT_DESCRIPTOR   stImportDesc;  // Стандартный дескриптор импорта.
    std::string               strModuleName; // Импортируемое имя модуля.
    std::vector<PEIMPORTFUNC> vecImportFunc; // Массив импортируемых функций.
};
using PEIMPORT_VEC = std::vector<PEIMPORT>;

Пример:

libpe::Clibpe pe(L"C:\\Windows\\notepad.exe");
const auto peImp = pe.GetImport();
if (!peImp) {
    return -1;
}

for (const auto& itModule : *peImp) { // Цикл по всем импортам, которые
                                      // содержит данный PE-файл.
    std::cout << std::format("{}, Imported funcs: {}\r\n", 
                   itModule.strModuleName, itModule.vecImportFunc.size());
    for (const auto& itFuncs : itModule.vecImportFunc) { // Цикл по всем функциям, импортированным из модуля itModule.
        itFuncs.strFuncName;       // Имя импортированной функции.
        itFuncs.stImpByName;       // Структура IMAGE_IMPORT_BY_NAME для этой функции.
        itFuncs.unThunk.stThunk32; // Объединение IMAGE_THUNK_DATA32 или 
                                   // IMAGE_THUNK_DATA64 (в зависимости от типа PE).
    }
}

GetResources

[[nodiscard]] auto GetResources()const->std::optional<PERESROOT>;

Возвращает все ресурсы файла.

Пример:

Следующий фрагмент кода заполняет std::wstring типами и именами всех ресурсов, которыми обладает файл PE, и выводит его в стандартный std::wcout.

#include <format>
#include <iostream>
#include <string>

import libpe;
using namespace libpe;

int main()
{
    libpe::Clibpe pe;
    if (pe.OpenFile(L"C:\\Windows\\notepad.exe") != PEOK) {
        return -1;
    }

    const auto peResRoot = pe.GetResources();
    if (!peResRoot) {
        return -1;
    }

    std::wstring wstrResData; // Этот wstring будет содержать все ресурсы по имени.
    for (const auto& iterRoot : peResRoot->vecResData) { // Основной цикл извлечения ресурсов.
        auto ilvlRoot = 0;
        auto pResDirEntry = &iterRoot.stResDirEntry; // КОРНЕВОЙ IMAGE_RESOURCE_DIRECTORY_ENTRY
        if (pResDirEntry->NameIsString) {
            wstrResData += std::format(L"Entry: {} 
                           [Name: {}]\r\n", ilvlRoot, iterRoot.wstrResName);
        }
        else {
            if (const auto iter = MapResID.find(pResDirEntry->Id); 
                                  iter != MapResID.end()) {
                wstrResData += std::format(L"Entry: {} 
                    [Id: {}, {}]\r\n", ilvlRoot, pResDirEntry->Id, iter->second);
            }
            else {
                wstrResData += std::format(L"Entry: {} 
                               [Id: {}]\r\n", ilvlRoot, pResDirEntry->Id);
            }
        }

        if (pResDirEntry->DataIsDirectory) {
            auto ilvl2 = 0;
            auto pstResLvL2 = &iterRoot.stResLvL2;
            for (const auto& iterLvL2 : pstResLvL2->vecResData) {
                pResDirEntry = &iterLvL2.stResDirEntry; // Уровень 2 
                                         // IMAGE_RESOURCE_DIRECTORY_ENTRY
                if (pResDirEntry->NameIsString) {
                    wstrResData += std::format
                    (L"    Entry: {}, Name: {}\r\n", ilvl2, iterLvL2.wstrResName);
                }
                else {
                    wstrResData += std::format
                    (L"    Entry: {}, Id: {}\r\n", ilvl2, pResDirEntry->Id);
                }

                if (pResDirEntry->DataIsDirectory) {
                    auto ilvl3 = 0;
                    auto pstResLvL3 = &iterLvL2.stResLvL3;
                    for (const auto& iterLvL3 : pstResLvL3->vecResData) {
                        pResDirEntry = &iterLvL3.stResDirEntry; // Уровень 3 
                                       // IMAGE_RESOURCE_DIRECTORY_ENTRY
                        if (pResDirEntry->NameIsString) {
                            wstrResData += std::format
                            (L"        Entry: {}, Name: {}\r\n", ilvl3, iterLvL3.wstrResName);
                        }
                        else {
                            wstrResData += std::format
                            (L"        Entry: {}, lang: {}\r\n", ilvl3, pResDirEntry->Id);
                        }
                        ++ilvl3;
                    }
                }
                ++ilvl2;
            }
        }
        ++ilvlRoot;
    }
    std::wcout << wstrResData;

GetExceptions

[[nodiscard]] auto GetExceptions()const->std::optional<PEEXCEPTION_VEC>;

Возвращает массив записей Исключений файла.

struct PEEXCEPTION {
    DWORD                         dwOffset;           //Смещение относительно начала 
                                                      //файла для дескриптора исключений.
    _IMAGE_RUNTIME_FUNCTION_ENTRY stRuntimeFuncEntry; //Стандартный 
                                                      //заголовок _IMAGE_RUNTIME_FUNCTION_ENTRY.
};
using PEEXCEPTION_VEC = std::vector<PEEXCEPTION>;

GetSecurity

[[nodiscard]] auto GetSecurity()const->std::optional<PESECURITY_VEC>;

Возвращает массив записей Безопасности файла.

struct PEWIN_CERTIFICATE { //Полная копия структуры WIN_CERTIFICATE 
                           //из <WinTrust.h>.
    DWORD dwLength;
    WORD  wRevision;
    WORD  wCertificateType;
    BYTE  bCertificate[1];
};
struct PESECURITY {
    DWORD             dwOffset;  //Смещение относительно начала файла для данного дескриптора безопасности.
    PEWIN_CERTIFICATE stWinSert; //Структура WIN_CERTIFICATE.
};
using PESECURITY_VEC = std::vector<PESECURITY>;

GetRelocations

[[nodiscard]] auto GetRelocations()const->std::optional<PERELOC_VEC>;

Возвращает массив информации о перемещениях файла.

struct PERELOCDATA {
    DWORD dwOffset;     //Смещение относительно начала файла для дескриптора данных о перемещении.
    WORD  wRelocType;   //Тип перемещения.
    WORD  wRelocOffset; //Смещение перемещения (смещение, к которому необходимо применить перемещение).
};
struct PERELOC {
    DWORD                    dwOffset;     //Смещение относительно начала файла для 
                                           //дескриптора перемещения.
    IMAGE_BASE_RELOCATION    stBaseReloc;  //Стандартный заголовок IMAGE_BASE_RELOCATION.
    std::vector<PERELOCDATA> vecRelocData; //Массив структур данных о перемещении.
};
using PERELOC_VEC = std::vector<PERELOC>;

GetDebug

[[nodiscard]] auto GetDebug()const->std::optional<PEDEBUG_VEC>;

Возвращает массив записей Отладки файла.

struct PEDEBUGDBGHDR {
    //dwHdr[6] - массив из первых шести DWORD 
    //данных IMAGE_DEBUG_DIRECTORY::PointerToRawData (заголовок отладочной информации).
    //Их значение зависит от значения dwHdr[0] (сигнатуры).
    //Если dwHdr[0] == 0x53445352 (Ascii "RSDS"), то это файл PDB 7.0:
    // Затем dwHdr[1]-dwHdr[4] - это GUID (*((GUID*)&dwHdr[1])). dwHdr[5] - счетчик/возраст.
    //Если dwHdr[0] == 0x3031424E (Ascii "NB10"), то это файл PDB 2.0:
    // Тогда dwHdr[1] - это смещение. dwHdr[2] - это время/сигнатура. dwHdr[3] - счетчик/возраст.
    DWORD       dwHdr[6];
    std::string strPDBName; //Имя/путь к файлу PDB.
};
struct PEDEBUG {
    DWORD                 dwOffset;       //Смещение относительно начала файла для дескриптора отладки.
    IMAGE_DEBUG_DIRECTORY stDebugDir;     //Стандартный заголовок IMAGE_DEBUG_DIRECTORY.
    PEDEBUGDBGHDR         stDebugHdrInfo; //Заголовок отладочной информации.
};
using PEDEBUG_VEC = std::vector<PEDEBUG>;

GetTLS

[[nodiscard]] auto GetTLS()const->std::optional<PETLS>;

Возвращает информацию о Местном хранилище потоков файла.

struct PETLS {
    DWORD              dwOffset;          //Смещение относительно начала файла для 
                                          //заголовка местного хранилища потоков.
    union UNPETLS {
    	IMAGE_TLS_DIRECTORY32 stTLSDir32; //Стандартный заголовок x86 для местного хранилища потоков.
    	IMAGE_TLS_DIRECTORY64 stTLSDir64; //Заголовок местного хранилища потоков x64.
    } unTLS;
    std::vector<DWORD> vecTLSCallbacks;   //Массив обратных вызовов местного хранилища потоков.
};

GetLoadConfig

[[nodiscard]] auto GetLoadConfig()const->std::optional;;

Возвращает информацию о каталоге Load Config файла.

struct PELOADCONFIG {
    DWORD dwOffset;                          // Файловый смещение описателя LCD.
    union UNPELOADCONFIG {
        IMAGE_LOAD_CONFIG_DIRECTORY32 stLCD32; // Описатель LCD x86.
        IMAGE_LOAD_CONFIG_DIRECTORY64 stLCD64; // Описатель LCD x64.
    } unLCD;
};

GetBoundImport

[[nodiscard]] auto GetBoundImport()const->std::optional;;

Возвращает массив записей Bound Import файла.

struct PEBOUNDFORWARDER {
    DWORD                     dwOffset;                // Файловое смещение описателя Bound Forwarder.
    IMAGE_BOUND_FORWARDER_REF stBoundForwarder;        // Стандартная структура IMAGE_BOUND_FORWARDER_REF.
    std::string               strBoundForwarderName;   // Имя привязанного экспорта.
};
struct PEBOUNDIMPORT {
    DWORD                         dwOffset;            // Файловое смещение описателя Bound Import.
    IMAGE_BOUND_IMPORT_DESCRIPTOR stBoundImpDesc;      // Стандартная структура IMAGE_BOUND_IMPORT_DESCRIPTOR.
    std::string                   strBoundName;        // Имя Bound Import.
    std::vector vecBoundForwarder;   // Массив структур Bound Forwarder.
};
using PEBOUNDIMPORT_VEC = std::vector;;

GetDelayImport

[[nodiscard]] auto GetDelayImport()const->std::optional;;

Возвращает массив записей Delay Import файла.

struct PEDELAYIMPORTFUNC {
    union UNPEDELAYIMPORTTHUNK {
        struct x32 {
            IMAGE_THUNK_DATA32 stImportAddressTable;       // x86 структура Import Address Table.
            IMAGE_THUNK_DATA32 stImportNameTable;          // x86 структура Import Name Table.
            IMAGE_THUNK_DATA32 stBoundImportAddressTable;  // x86 структура Bound Import Address Table.
            IMAGE_THUNK_DATA32 stUnloadInformationTable;   // x86 структура Unload Information Table.
        } st32;
        struct x64 {
            IMAGE_THUNK_DATA64 stImportAddressTable;       // x64 структура Import Address Table.
            IMAGE_THUNK_DATA64 stImportNameTable;          // x64 структура Import Name Table.
            IMAGE_THUNK_DATA64 stBoundImportAddressTable;  // x64 структура Bound Import Address Table.
            IMAGE_THUNK_DATA64 stUnloadInformationTable;   // x64 структура Unload Information Table.
        } st64;
    } unThunk;
    IMAGE_IMPORT_BY_NAME stImpByName; // Стандартная структура IMAGE_IMPORT_BY_NAME.
    std::string          strFuncName; // Имя функции.
};
struct PEDELAYIMPORT {
    DWORD                          dwOffset;        // Файловое смещение этого записи Delay Import.
    IMAGE_DELAYLOAD_DESCRIPTOR     stDelayImpDesc;  // Стандартная структура IMAGE_DELAYLOAD_DESCRIPTOR.
    std::string                    strModuleName;   // Имя импортируемого модуля.
    std::vector vecDelayImpFunc; // Массив функций модуля Delay Import.
};
using PEDELAYIMPORT_VEC = std::vector;;

GetCOMDescriptor

[[nodiscard]] auto GetCOMDescriptor()const->std::optional;;

Возвращает информацию о файле .NET.

struct PECOMDESCRIPTOR {
    DWORD              dwOffset; // Файловое смещение описателя IMAGE_COR20_HEADER.
    IMAGE_COR20_HEADER stCorHdr; // Стандартная структура IMAGE_COR20_HEADER.
};

Вспомогательные методы

Эти автономные методы не требуют активного объекта Clibpe с открытым файлом. Вместо этого они принимают ссылки на ранее полученные структуры.

GetFileType

[[nodiscard]] inline constexpr auto GetFileType(const PENTHDR& stNTHdr)->EFileType

Возвращает тип файла PE в виде перечисления EFileType.

enum class EFileType : std::uint8_t {
    UNKNOWN = 0, PE32, PE64, PEROM
};

GetImageBase

[[nodiscard]] inline constexpr auto GetImageBase(const PENTHDR& stNTHdr)->ULONGLONG

Возвращает базовый адрес файла.

GetOffsetFromRVA

[[nodiscard]] inline constexpr auto GetOffsetFromRVA
              (ULONGLONG ullRVA, const PESECHDR_VEC& vecSecHdr)->DWORD

Преобразует RVA файла в физическое смещение файла на диске.

FlatResources

[[nodiscard]] inline constexpr auto FlatResources(const PERESROOT& stResRoot)

Эта функция является своего рода упрощенной версией метода GetResources. Она принимает структуру PERESROOT, возвращаемую методом GetResources, и возвращает std::vector структур PERESFLAT. PERESFLAT – это легкая структура, которая содержит только указатели на фактические данные ресурсов, в отличие от тяжелой структуры PERESROOT. Функция FlatResources сглаживает все ресурсы, делая доступ к ним более удобным.

struct PERESFLAT {
    std::span<const std::byte> spnData { };    // Данные ресурса.
    std::wstring_view          wsvTypeStr { }; // Название типа ресурса.
    std::wstring_view          wsvNameStr { }; // Название ресурса (собственное имя ресурса).
    std::wstring_view          wsvLangStr { }; // Название языка ресурса.
    WORD                       wTypeID { };    // Идентификатор типа ресурса
                                               //(RT_CURSOR, RT_BITMAP и т.д.).
    WORD                       wNameID { };    // Идентификатор имени ресурса (собственный идентификатор ресурса).
    WORD                       wLangID { };    // Идентификатор языка ресурса.
};
using PERESFLAT_VEC = std::vector<PERESFLAT>;

Maps

Файл PE состоит из множества структур, которые в свою очередь содержат множество полей, некоторые из которых имеют предопределенные значения. Эти карты предназначены для упрощения преобразования таких полей в формат, понятный человеку. Они представляют собой простые карты std::unordered_map<DWORD, std::wstring_view>.

Обратите внимание, что некоторые поля могут иметь только одно значение, в то время как другие могут объединять множество значений с помощью побитовой операции или |.

MapFileHdrMachine

Эта карта формирует одно из значений поля IMAGE_NT_HEADERS::IMAGE_FILE_HEADER::Machine.

MapFileHdrCharact

Эта карта формирует одно или несколько значений из поля IMAGE_NT_HEADERS::IMAGE_FILE_HEADER::Characteristics.

const auto pNTHdr = m_pLibpe->GetNTHeader();
const auto pDescr = &pNTHdr->unHdr.stNTHdr32.FileHeader; //Одинаково для x86/x64.
std::wstring  wstrCharact;
for (const auto& flags : MapFileHdrCharact) {
    if (flags.first & pDescr->Characteristics) {
        wstrCharact += flags.second;
        wstrCharact += L"\n";
    }
}

MapOptHdrMagic

Эта карта формирует одно из значений поля IMAGE_NT_HEADERS::IMAGE_OPTIONAL_HEADER::Magic.

MapOptHdrSubsystem

Эта карта формирует одно из значений поля IMAGE_NT_HEADERS::IMAGE_OPTIONAL_HEADER::Subsystem.

MapOptHdrDllCharact

Эта карта формирует одно или несколько значений из поля IMAGE_NT_HEADERS::IMAGE_OPTIONAL_HEADER::DllCharacteristics.

const auto pNTHdr = m_pLibpe->GetNTHeader();
const auto pOptHdr = &pNTHdr->unHdr.stNTHdr32.OptionalHeader //For x64: 
                                    //pNTHdr->unHdr.stNTHdr64.OptionalHeader
std::wstring wstrCharact;
for (const auto& flags : MapOptHdrDllCharact) {
    if (flags.first & pOptHdr->DllCharacteristics) {
        wstrCharact += flags.second;
        wstrCharact += L"\n";
    }
}

MapSecHdrCharact

Этот map формирует одно или несколько значений из поля IMAGE_SECTION_HEADER::Characteristics.

const auto pSecHeaders = m_pLibpe->GetSecHeaders();
std::wstring wstrCharact;
auto IdOfSection = 0; //ID нужной секции.
for (const auto& flags : MapSecHdrCharact) {
    if (flags.first & pSecHeaders->at(IdOfSection).stSecHdr.Characteristics) {
        wstrCharact += flags.second;
        wstrCharact += L"\n";
    }
}

MapResID

Этот map формирует одно из значений из поля IMAGE_RESOURCE_DIRECTORY_ENTRY::Id.

MapWinCertRevision

Этот map формирует одно из значений из поля WIN_CERTIFICATE::wRevision.

MapWinCertType

Этот map формирует одно из значений из поля WIN_CERTIFICATE::wCertificateType.

MapRelocType

Этот map формирует одно из значений из поля PERELOCDATA::wRelocType.

MapDbgType

Этот map формирует одно из значений из поля IMAGE_DEBUG_DIRECTORY::Type.

MapTLSCharact

Этот map формирует одно из значений из поля IMAGE_TLS_DIRECTORY::Characteristics.

MapLCDGuardFlags

Этот map формирует одно или несколько значений из поля IMAGE_LOAD_CONFIG_DIRECTORY::GuardFlags.

const auto pLCD = m_pLibpe->GetLoadConfig();
const auto pPELCD = &pLCD->unLCD.stLCD32; //For x64: pLCD->unLCD.stLCD64
std::wstring wstrGFlags;
for (const auto& flags : MapLCDGuardFlags) {
    if (flags.first & pPELCD->GuardFlags) {
        wstrGFlags += flags.second;
        wstrGFlags += L"\n";
    }
}

MapCOR20Flags

Этот map формирует одно или несколько значений из поля IMAGE_COR20_HEADER::Flags.

const auto pCOMDesc = m_pLibpe->GetCOMDescriptor();
std::wstring wstrFlags;
for (const auto& flags : MapCOR20Flags) {
    if (flags.first & pCOMDesc->stCorHdr.Flags) {
        wstrFlags += flags.second;
        wstrFlags += L"\n";
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *