C库读取Linux的EXE版本? [英] C library to read EXE version from Linux?

查看:114
本文介绍了C库读取Linux的EXE版本?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有一个图书馆,我可以在Linux上使用,它将返回在Explorer的版本选项卡中列出一个Windows EXE文件的属性?这些都是像产品名称,产品版本,描述等领域。

Is there a library I can use in Linux that will return the properties of a Windows EXE file that are listed in Explorer's Version tab? These are fields like Product Name, Product Version, Description, etc.

有关我的项目,EXE文件只能从内存中读取,而不是从一个文件。我想,以避免写入EXE文件到硬盘。

For my project, the EXE file can be only read from memory, not from a file. I would like to avoid writing the EXE file to disk.

推荐答案

该文件的版本是在 VS_FIXEDFILEINFO 结构,但你必须要找到它变成可执行数据。有你想要什么做的两种方式:

The version of the file is in the VS_FIXEDFILEINFO struct, but you have to find it into the executable data. There are two ways of doing what you want:


  1. 搜索文件中的VERSION_INFO签名和阅读 VS_FIXEDFILEINFO 结构直接。

  2. 找到的 .rsrc 部分,解析资源树,找到 RT_VERSION 资源,分析它,并提取 VS_FIXEDFILEINFO 数据。

  1. Search for the VERSION_INFO signature in the file and read the VS_FIXEDFILEINFO struct directly.
  2. Find the .rsrc section, parse the resource tree, find the RT_VERSION resource, parse it and extract the VS_FIXEDFILEINFO data.

第一个是比较容易的,但容易被放错了地方机会找到签名。此外,你问(产品名称,说明等),其他数据都没有在这个结构中,所以我会尽力解释如何获取数据硬盘的方式。

The first one is easier, but susceptible to find the signature by chance in the wrong place. Moreover, the other data you ask for (product name, description, etc.) are not in this structure, so I'll try to explain how to obtain the data the hard way.

的PE格式是有点令人费解,所以我贴一块的code片,有评论,并以最短的错误检查。我会写一个简单的函数,数据转储到标准输出。写它作为一个适当的功能是作为练习留给读者:)

The PE format is a bit convoluted so I'm pasting the code piece by piece, with comments, and with minimum error checking. I'll write a simple function that dumps the data to the standard output. Writing it as a proper function is left as an exercise to the reader :)

请注意,我会直接使用偏移在缓冲区不是映射的结构,以避免相关的结构域的对齐或填充可移植性问题。无论如何,我已经注明所使用的结构类型(见包括对细节的文件WINNT.H)。

Note that I will be using offsets in the buffer instead of mapping the structs directly to avoid portability problems related to the alignment or padding of the struct fields. Anyway, I've annotated the type of the structs used (see include file winnt.h for details).

一是一些有用的声明,他们应该是不言自明的:

First a few useful declarations, they should be self-explanatory:

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)

然后就是发现可执行映像中的版本资源(无大小检查)的功能。

Then a function that finds the Version resource in the executable image (no size checks).

const char *FindVersion(const char *buf)
{

在EXE的第一个结构是MZ头(与MS-DOS兼容)。

The first structure in the EXE is the MZ header (for compatibility with MS-DOS).

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

在MZ头有趣的唯一字段是偏移PE header的。 PE头是真实的东西。

The only field interesting in the MZ header is the offset of the PE header. The PE header is the real thing.

    //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头,拥有所有的符号数据。

Actually, the PE header is quite boring, we want the COFF header, that have all the symbolic data.

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

我们只需要以下字段从这个。

We just need the following fields from this one.

    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位。

The optional header is actually mandatory in an EXE and it is just after the COFF. The magic is different for 32 and 64 bits Windows. I'm assuming 32 bits from here on.

    //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段的元数据

Here comes the interesting part: we want to find the resources section. It has two parts: 1. the section data, 2. the section metadata.

的数据的位置是在可选的报头的末尾的表,并且每个部分具有在该表中的一个公知的指数。资源部分是在索引2中,所以我们得到资源段的虚拟地址(VA)其中:

The data location is in a table at the end of the optional header, and each section has a well known index in this table. Resource section is in index 2, so we obtain the virtual address (VA) of the resource section with:

    //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

To get the section metadata we need to iterate the section table looking for a section named .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和该部分的偏移量文件(也截面的大小,但我没有检查它!):

The section struct has two relevant members: the VA of the section and the offset of the section into the file (also the size of the section, but I'm not checking it!):

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

现在的对应于瓦雷斯 VA之前我们拿到了文件中的偏移量是容易的。

Now the offset in the file that correspond to the vaRes VA we got before is easy.

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

这是一个指向资源数据。所有的单个资源都设置在一个树的形式,具有3个级别:1)类型的资源,2)资源的标识,3)资源的语言。对于版本,我们将得到正确类型十分第一个。

This is a pointer to the resource data. All the individual resources are set up in the form of a tree, with 3 levels: 1) type of resource, 2) identifier of resource, 3) language of resource. For the version we will get the very first one of the correct type.

首先,我们有一个资源目录(资源类型),我们得到目录中的条目的数量,既有名和无名和迭代:

First, we have a resource directory (for the type of resource), we get the number of entries in the directory, both named and unnamed and iterate:

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

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

有关每个资源项,我们得到的资源的类型,并丢弃它,如果它不是RT_VERSION常数(16)

For each resource entry we get the type of the resource and discard it if it is not the RT_VERSION constant (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到达树的下一个资源目录:

If it is a RT_VERSION we get to the next resource directory in the tree:

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

和继续到下一级目录,我们不关心的ID。这其中的:

And go on to the next directory level, we don't care about the id. of this one:

            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;

第三级具有的资源的语言。我们不在乎要么,所以只要抓住第一个:

The third level has the language of the resource. We don't care either, so just grab the first one:

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

和我们得到的真正的资源,那么,实际上包含真正的资源的位置和大小,而是一个结构,我们不关心的大小。

And we get to the real resource, well, actually a struct that contains the location and size of the real resource, but we don't care about the size.

            DWORD verVa = READ_DWORD(verDir);

这就是VA版本种源,即转换成指针容易的

That's the VA of the version resouce, that is converted into a pointer easily.

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

和完成!如果没有找到返回 NULL

And done! If not found return NULL.

        }
        return NULL;
    }
    return NULL;
}

现在的版本资源被发现,我们要分析它。它实际上是一个树(还有什么)的双名/值。有些值是众所周知的,这是你在找什么,只是做一些测试,你会找出哪些。

Now that the version resource is found, we have to parse it. It is actually a tree (what else) of pairs "name" / "value". Some values are well known and that's what you are looking for, just do some test and you will find out which ones.

注意:所有的字符串存储在UNI code(UTF-16),但我的样本code的确是喑哑的转换成ASCII。此外,对于溢出没有检查。

NOTE: All strings are stored in UNICODE (UTF-16) but my sample code does the dumb conversion into ASCII. Also, no checks for overflow.

该函数的指针版本资源和偏移到内存中(0对于初学者),并返回分析的字节数。

The function takes the pointer to the version resource and the offset into this memory (0 for starters) and returns the number of bytes analyzed.

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

的所有偏移首先必须是4的倍数。

First of all the offset have to be a multiple of 4.

    offs = PAD(offs);

然后我们拿到的版本树节点的属性。

Then we get the properties of the version tree node.

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

节点的名称是一个统一code 0结尾的字符串。

The name of the node is a Unicode zero-terminated string.

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

更多的填充,如果neccesary:

More padding, if neccesary:

    offs = PAD(offs);

如果键入不为0,那么它是一个字符串形式的数据。

If type is not 0, then it is a string version data.

    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 the name is VS_VERSION_INFO then it is a VS_FIXEDFILEINFO struct. Else it is binary data.

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

现在做递归调用打印完整的树。

Now do the recursive call to print the full tree.

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

和返回之前,一些填充。

And some more padding before returning.

    return PAD(offs);
}

最后,作为奖励,一个功能。

int main(int argc, char **argv)
{
    struct stat st;
    if (stat(argv[1], &st) < 0)
    {
        perror(argv[1]);
        return 1;
    }

    char *buf = malloc(st.st_size);

    FILE *f = fopen(argv[1], "r");
    if (!f)
    {
        perror(argv[1]);
        return 2;
    }

    fread(buf, 1, st.st_size, f);
    fclose(f);

    const char *version = FindVersion(buf);
    if (!version)
        printf("No version\n");
    else
        PrintVersion(version, 0);
    return 0;
}

我曾与一些随机的EXE测试,它似乎工作得很好。

I've tested it with a few random EXEs and it seems to work just fine.

这篇关于C库读取Linux的EXE版本?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆