C библиотека для чтения EXE-версии из Linux?

есть ли библиотека, которую я могу использовать в Linux, которая вернет свойства EXE-файла Windows, которые перечислены на вкладке версии проводника? Это такие поля, как название продукта, версия продукта, описание и т. д.

для моего проекта, EXE-файл можно только читать из памяти, а не из файла. Я хотел бы избежать записи EXE-файла на диск.

5 ответов


версия файла находится в VS_FIXEDFILEINFO struct, но вы должны найти его в исполняемый данных. Есть два способа сделать то, что вы хотите:

  1. найдите подпись VERSION_INFO в файле и прочитайте VS_FIXEDFILEINFO структура напрямую.
  2. найти парсят дереве ресурсов, найти RT_VERSION ресурс, проанализируйте его и извлеките VS_FIXEDFILEINFO данные.

первый легче, но восприимчив к поиску подпись случайно оказалась не в том месте. Кроме того, другие данные, которые вы просите (название продукта, описание и т. д.) не находятся в этой структуре, поэтому я попытаюсь объяснить, как получить данные сложным способом.

формат PE немного запутан, поэтому я вставляю код по частям, с комментариями и с минимальной проверкой ошибок. Я напишу простую функцию, которая сбрасывает данные в стандартный вывод. Написание его как правильной функции оставлено как упражнение для читателя :)

обратите внимание, что я буду использовать смещения в буфере вместо непосредственного сопоставления структур, чтобы избежать проблем переносимости, связанных с выравниванием или заполнением полей структуры. Во всяком случае, я аннотировал тип используемых структур (см. include file winnt.H для деталей).

сначала несколько полезных заявлений, они должны быть самоочевидны:

typedef uint32_t DWORD;
typedef uint16_t WORD;
typedef uint8_t BYTE;

#define READ_BYTE(p) (((unsigned char*)(p))[0])
#define READ_WORD(p) ((((unsigned char*)(p))[0]) | ((((unsigned char*)(p))[1]) << 8))
#define READ_DWORD(p) ((((unsigned char*)(p))[0]) | ((((unsigned char*)(p))[1]) << 8) | \
    ((((unsigned char*)(p))[2]) << 16) | ((((unsigned char*)(p))[3]) << 24))

#define PAD(x) (((x) + 3) & 0xFFFFFFFC)

затем функция, которая находит ресурс версии в исполняемом образе (без размера проверяет.)

const char *FindVersion(const char *buf)
{

первой структурой в EXE является заголовок MZ (для совместимости С MS-DOS).

    //buf is a IMAGE_DOS_HEADER
    if (READ_WORD(buf) != 0x5A4D) //MZ signature
        return NULL;

единственное поле, интересное в заголовке MZ, - это смещение заголовка PE. Заголовок PE-это настоящая вещь.

    //pe is a IMAGE_NT_HEADERS32
    const char *pe = buf + READ_DWORD(buf + 0x3C);
    if (READ_WORD(pe) != 0x4550) //PE signature
        return NULL;

на самом деле, заголовок PE довольно скучный, мы хотим заголовок COFF, который имеет все символические данные.

    //coff is a IMAGE_FILE_HEADER
    const char *coff = pe + 4;

нам просто нужны следующие поля из этого.

    WORD numSections = READ_WORD(coff + 2);
    WORD optHeaderSize = READ_WORD(coff + 16);
    if (numSections == 0 || optHeaderSize == 0)
        return NULL;

в необязательный заголовок на самом деле является обязательным в EXE, и это сразу после COFF. Магия отличается для 32 и 64 бит Windows. Я предполагаю 32 бита отсюда.

    //optHeader is a IMAGE_OPTIONAL_HEADER32
    const char *optHeader = coff + 20;
    if (READ_WORD(optHeader) != 0x10b) //Optional header magic (32 bits)
        return NULL;

вот интересная часть: мы хотим найти раздел ресурсов. Он состоит из двух частей: 1. данные раздела 2. раздел метаданные.

расположение данных находится в таблице в конце необязательного заголовка, и каждый раздел имеет хорошо известный индекс в этой таблице. Раздел ресурса в индекс 2, поэтому мы получаем виртуальный адрес (VA) раздела ресурсов с помощью:

    //dataDir is an array of IMAGE_DATA_DIRECTORY
    const char *dataDir = optHeader + 96;
    DWORD vaRes = READ_DWORD(dataDir + 8*2);

    //secTable is an array of IMAGE_SECTION_HEADER
    const char *secTable = optHeader + optHeaderSize;

чтобы получить метаданные раздела, нам нужно повторить таблицу разделов, ища раздел с именем .rsrc.

    int i;
    for (i = 0; i < numSections; ++i)
    {
        //sec is a IMAGE_SECTION_HEADER*
        const char *sec = secTable + 40*i;
        char secName[9];
        memcpy(secName, sec, 8);
        secName[8] = 0;

        if (strcmp(secName, ".rsrc") != 0)
            continue;

структура раздела имеет два соответствующих члена: VA раздела и смещение раздела в файл (также размер раздела, но я не проверяю его!):

        DWORD vaSec = READ_DWORD(sec + 12);
        const char *raw = buf + READ_DWORD(sec + 20);

теперь смещение в файле, которая соответствует vaRes Ва мы получили раньше легко.

        const char *resSec = raw + (vaRes - vaSec);

это указатель на данные ресурса. Все отдельные ресурсы устанавливаются в виде дерева, с 3 уровнями: 1) тип ресурса, 2) идентификатор ресурса, 3) язык ресурса. Для версии мы получим самый первый из правильного типа.

во-первых, у нас есть каталог ресурсов (для типа ресурса), мы получаем количество записей в каталоге, как именованных, так и неназванных и повторите:

        WORD numNamed = READ_WORD(resSec + 12);
        WORD numId = READ_WORD(resSec + 14);

        int j;
        for (j = 0; j < numNamed + numId; ++j)
        {

для каждой записи ресурса мы получаем тип ресурса и отбрасываем его, если он не является константой RT_VERSION (16).

            //resSec is a IMAGE_RESOURCE_DIRECTORY followed by an array
            // of IMAGE_RESOURCE_DIRECTORY_ENTRY
            const char *res = resSec + 16 + 8 * j;
            DWORD name = READ_DWORD(res);
            if (name != 16) //RT_VERSION
                continue;

если это RT_VERSION, мы переходим к следующему каталогу ресурсов в дереве:

            DWORD offs = READ_DWORD(res + 4);
            if ((offs & 0x80000000) == 0) //is a dir resource?
                return NULL;
            //verDir is another IMAGE_RESOURCE_DIRECTORY and 
            // IMAGE_RESOURCE_DIRECTORY_ENTRY array
            const char *verDir = resSec + (offs & 0x7FFFFFFF);

и перейти на следующий уровень каталога, мы не заботимся о ИД. из этого:

            numNamed = READ_WORD(verDir + 12);
            numId = READ_WORD(verDir + 14);
            if (numNamed == 0 && numId == 0)
                return NULL;
            res = verDir + 16;
            offs = READ_DWORD(res + 4);
            if ((offs & 0x80000000) == 0) //is a dir resource?
                return NULL;

третий уровень имеет язык ресурса. Нам тоже все равно, так что хватай первый. один:

            //and yet another IMAGE_RESOURCE_DIRECTORY, etc.
            verDir = resSec + (offs & 0x7FFFFFFF);                    
            numNamed = READ_WORD(verDir + 12);
            numId = READ_WORD(verDir + 14);
            if (numNamed == 0 && numId == 0)
                return NULL;
            res = verDir + 16;
            offs = READ_DWORD(res + 4);
            if ((offs & 0x80000000) != 0) //is a dir resource?
                return NULL;
            verDir = resSec + offs;

и мы добираемся до реального ресурса, ну, на самом деле структура, которая содержит местоположение и размер реального ресурса, но нас не волнует размер.

            DWORD verVa = READ_DWORD(verDir);

это VA версии resouce,который легко преобразуется в указатель.

            const char *verPtr = raw + (verVa - vaSec);
            return verPtr;

и Done! Если не найдено return NULL.

        }
        return NULL;
    }
    return NULL;
}

теперь, когда ресурс версии найден, мы должны проанализировать его. На самом деле это дерево (что еще) пар "значение name." Некоторые ценности хорошо известны, и это то, что вы ищете, просто сделайте некоторый тест, и вы узнаете, какие из них.

Примечание: все строки хранятся в UNICODE (UTF-16), но мой пример кода выполняет немое преобразование в ASCII. Кроме того, нет проверок на переполнение.

функция принимает указатель на ресурс версии и смещение в эту память (0 для стартеров) и возвращает количество байтов проанализированный.

int PrintVersion(const char *version, int offs)
{

прежде всего смещение должно быть кратным 4.

    offs = PAD(offs);

затем мы получаем свойства узла дерева версий.

    WORD len    = READ_WORD(version + offs);
    offs += 2;
    WORD valLen = READ_WORD(version + offs);
    offs += 2;
    WORD type   = READ_WORD(version + offs);
    offs += 2;

имя узла-строка с нулевым завершением в Юникоде.

    char info[200];
    int i;
    for (i=0; i < 200; ++i)
    {
        WORD c = READ_WORD(version + offs);
        offs += 2;

        info[i] = c;
        if (!c)
            break;
    }

больше прокладки, если необходимо:

    offs = PAD(offs);

если type не 0, тогда это данные Строковой версии.

    if (type != 0) //TEXT
    {
        char value[200];
        for (i=0; i < valLen; ++i)
        {
            WORD c = READ_WORD(version + offs);
            offs += 2;
            value[i] = c;
        }
        value[i] = 0;
        printf("info <%s>: <%s>\n", info, value);
    }

еще, если имя VS_VERSION_INFO, то это VS_FIXEDFILEINFO структура. Иначе это двоичные данные.

    else
    {
        if (strcmp(info, "VS_VERSION_INFO") == 0)
        {

я просто печатаю версию файла и продукта, но вы можете легко найти другие поля этой структуры. Остерегайтесь смешанный прямой порядок.

            //fixed is a VS_FIXEDFILEINFO
            const char *fixed = version + offs;
            WORD fileA = READ_WORD(fixed + 10);
            WORD fileB = READ_WORD(fixed + 8);
            WORD fileC = READ_WORD(fixed + 14);
            WORD fileD = READ_WORD(fixed + 12);
            WORD prodA = READ_WORD(fixed + 18);
            WORD prodB = READ_WORD(fixed + 16);
            WORD prodC = READ_WORD(fixed + 22);
            WORD prodD = READ_WORD(fixed + 20);
            printf("\tFile: %d.%d.%d.%d\n", fileA, fileB, fileC, fileD);
            printf("\tProd: %d.%d.%d.%d\n", prodA, prodB, prodC, prodD);
        }
        offs += valLen;
    }

теперь выполните рекурсивный вызов для печати полного дерева.

    while (offs < len)
        offs = PrintVersion(version, offs);

и еще несколько прокладок перед возвращением.

    return PAD(offs);
}

наконец, в качестве бонуса, a


Я знаю pev Это инструмент на Ubuntu, который позволяет вам видеть эту информацию, а также много другой информации заголовка PE. Я также знаю, что это написано в с. возможно, вы захотите взглянуть на него. Немного от ее раздел История в документации:

ПЭВ родился в 2010 году от простой потребности: программа, чтобы узнать версия (версия файла) файла PE32, который может быть запущен в Linux. Этот номер версии хранится в ресурсах (.rsrc) раздел но время, когда мы решили просто искать строку в целом двоичный, без какой-либо оптимизации.

позже мы решили проанализировать файл PE32 до достижения .rsrc раздел и получить поле версия файла. Для этого мы понял, что мы должны разобрать весь файл, и мы подумали, если бы мы могли распечатайте также все поля и значения...

до версии 0.40, pev была уникальной программой для разбора заголовков PE и разделы (теперь readpe несет ответственность за это). В версии 0.50 мы ориентирован на анализ вредоносных программ и разбит на различные программы за библиотекой под названием либпе. В настоящее время все программы pev используют libpe.


установить winelib http://www.winehq.org/docs/winelib-guide/index Это порт MS Windows API для других систем, включая linux.

затем используйте MS Windows API. Как GetFileVersionInfo http://msdn.microsoft.com/en-us/library/windows/desktop/ms647003 (v=против 85).aspx
или любые другие функции.

Я никогда этого не делал, но я бы начал с этих выводов.

Что касается exe-файла в ограничении памяти, вы можете скопировать его в ОЗУ диск?


вот патч для кода для поддержки PE32+. Протестировано на некоторых файлах и, похоже, работает.

//optHeader is a IMAGE_OPTIONAL_HEADER32
const char *optHeader = coff + 20;
WORD magic = READ_WORD(optHeader);
if (magic != 0x10b && magic != 0x20b)
    return NULL;

//dataDir is an array of IMAGE_DATA_DIRECTORY
const char *dataDir = optHeader + (magic==0x10b ? 96: 112);
DWORD vaRes = READ_DWORD(dataDir + 8*2);

вот пример на языке Tcl для анализа через a .EXE-файл для получения информации о версии.

чтение информации о версии из исполняемых файлов Win32.

этой веб-страница описывает .заголовок exe-формате. Я не уверен в дате этой информации или же она относится к более поздним версиям Windows или нет. Однако это отправная точка.