C库从Linux读取EXE版本?

cra*_*535 10 c linux windows portable-executable

我可以在Linux中使用哪个库来返回Explorer的Version选项卡中列出的Windows EXE文件的属性?这些是产品名称,产品版本,描述等字段.

对于我的项目,EXE文件只能从内存中读取,而不能从文件中读取.我想避免将EXE文件写入磁盘.

rod*_*igo 23

该文件的版本在VS_FIXEDFILEINFO结构中,但您必须在可执行数据中找到它.有两种方法可以做你想做的事:

  1. 在文件中搜索VERSION_INFO签名并VS_FIXEDFILEINFO直接读取结构.
  2. 找到该.rsrc部分,解析资源树,找到RT_VERSION资源,解析它并提取VS_FIXEDFILEINFO数据.

第一个更容易,但很容易在错误的地方偶然发现签名.此外,您要求的其他数据(产品名称,描述等)不在此结构中,因此我将尝试解释如何以困难的方式获取数据.

PE格式有点复杂,所以我逐个粘贴代码,注释和最小错误检查.我将编写一个将数据转储到标准输出的简单函数.把它写成一个合适的函数留给读者一个练习:)

请注意,我将在缓冲区中使用偏移而不是直接映射结构,以避免与结构字段的对齐或填充相关的可移植性问题.无论如何,我已经注释了所使用的结构的类型(有关详细信息,请参阅包含文件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)
Run Code Online (Sandbox Code Playgroud)

然后是一个在可执行映像中找到Version资源的函数(没有大小检查).

const char *FindVersion(const char *buf)
{
Run Code Online (Sandbox Code Playgroud)

EXE中的第一个结构是MZ头(用于与MS-DOS兼容).

    //buf is a IMAGE_DOS_HEADER
    if (READ_WORD(buf) != 0x5A4D) //MZ signature
        return NULL;
Run Code Online (Sandbox Code Playgroud)

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;
Run Code Online (Sandbox Code Playgroud)

实际上,PE头很无聊,我们想要COFF头,它具有所有符号数据.

    //coff is a IMAGE_FILE_HEADER
    const char *coff = pe + 4;
Run Code Online (Sandbox Code Playgroud)

我们只需要这个字段中的以下字段.

    WORD numSections = READ_WORD(coff + 2);
    WORD optHeaderSize = READ_WORD(coff + 16);
    if (numSections == 0 || optHeaderSize == 0)
        return NULL;
Run Code Online (Sandbox Code Playgroud)

可选标头在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;
Run Code Online (Sandbox Code Playgroud)

这是有趣的部分:我们想找到资源部分.它有两部分: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;
Run Code Online (Sandbox Code Playgroud)

要获取节元数据,我们需要迭代节表以查找名为的节.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;
Run Code Online (Sandbox Code Playgroud)

section结构有两个相关的成员:section的VA和该部分到文件的偏移量(也是该部分的大小,但我没有检查它!):

        DWORD vaSec = READ_DWORD(sec + 12);
        const char *raw = buf + READ_DWORD(sec + 20);
Run Code Online (Sandbox Code Playgroud)

现在,文件中与vaRes我们之前获得的VA 相对应的偏移很容易.

        const char *resSec = raw + (vaRes - vaSec);
Run Code Online (Sandbox Code Playgroud)

这是指向资源数据的指针.所有单个资源都以树的形式建立,具有3个级别:1)资源类型,2)资源标识符,3)资源语言.对于该版本,我们将获得正确类型的第一个.

首先,我们有一个资源目录(对于资源类型),我们获取目录中的条目数,包括named和unnamed以及iterate:

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

        int j;
        for (j = 0; j < numNamed + numId; ++j)
        {
Run Code Online (Sandbox Code Playgroud)

对于每个资源条目,我们获取资源的类型,如果它不是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;
Run Code Online (Sandbox Code Playgroud)

如果它是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);
Run Code Online (Sandbox Code Playgroud)

继续下一个目录级别,我们不关心id.这一个:

            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;
Run Code Online (Sandbox Code Playgroud)

第三级具有资源的语言.我们也不关心,所以抓住第一个:

            //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;
Run Code Online (Sandbox Code Playgroud)

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

            DWORD verVa = READ_DWORD(verDir);
Run Code Online (Sandbox Code Playgroud)

这是版本资源的VA,很容易转换成指针.

            const char *verPtr = raw + (verVa - vaSec);
            return verPtr;
Run Code Online (Sandbox Code Playgroud)

并做了!如果没有找到返回NULL.

        }
        return NULL;
    }
    return NULL;
}
Run Code Online (Sandbox Code Playgroud)

现在找到了版本资源,我们必须解析它.它实际上是一对(名称)/"值"对的树(还有什么).一些值是众所周知的,这就是你正在寻找的,只需做一些测试,你会发现哪些.

注意:所有字符串都存储在UNICODE(UTF-16)中,但我的示例代码将dumb转换为ASCII.此外,没有检查溢出.

该函数将指向版本资源的指针和偏移量引入此内存(启动器为0)并返回分析的字节数.

int PrintVersion(const char *version, int offs)
{
Run Code Online (Sandbox Code Playgroud)

首先,偏移量必须是4的倍数.

    offs = PAD(offs);
Run Code Online (Sandbox Code Playgroud)

然后我们得到版本树节点的属性.

    WORD len    = READ_WORD(version + offs);
    offs += 2;
    WORD valLen = READ_WORD(version + offs);
    offs += 2;
    WORD type   = READ_WORD(version + offs);
    offs += 2;
Run Code Online (Sandbox Code Playgroud)

节点的名称是Unicode以零结尾的字符串.

    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;
    }
Run Code Online (Sandbox Code Playgroud)

如果需要更多填充:

    offs = PAD(offs);
Run Code Online (Sandbox Code Playgroud)

如果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);
    }
Run Code Online (Sandbox Code Playgroud)

否则,如果名称是,VS_VERSION_INFO那么它是一个VS_FIXEDFILEINFO结构.否则它是二进制数据.

    else
    {
        if (strcmp(info, "VS_VERSION_INFO") == 0)
        {
Run Code Online (Sandbox Code Playgroud)

我只是打印文件和产品的版本,但您可以轻松找到此结构的其他字段.请注意混合字节顺序.

            //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;
    }
Run Code Online (Sandbox Code Playgroud)

现在进行递归调用以打印完整的树.

    while (offs < len)
        offs = PrintVersion(version, offs);
Run Code Online (Sandbox Code Playgroud)

还有一些填充之前返回.

    return PAD(offs);
}
Run Code Online (Sandbox Code Playgroud)

最后,作为奖励,一个main功能.

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;
}
Run Code Online (Sandbox Code Playgroud)

我用一些随机的EXE测试了它,它似乎工作得很好.