我可以在Linux中使用哪个库,它将返回Explorer版本选项卡中列出的Windows EXE文件的属性?这些是像产品名称,产品版本,说明等字段。C库从Linux读取EXE版本?
对于我的项目,EXE文件只能从内存中读取,而不能从文件中读取。我想避免将EXE文件写入磁盘。
我可以在Linux中使用哪个库,它将返回Explorer版本选项卡中列出的Windows EXE文件的属性?这些是像产品名称,产品版本,说明等字段。C库从Linux读取EXE版本?
对于我的项目,EXE文件只能从内存中读取,而不能从文件中读取。我想避免将EXE文件写入磁盘。
该文件的版本位于VS_FIXEDFILEINFO
结构中,但您必须将其找到可执行数据中。有两种方法可以做你想做的事:
VS_FIXEDFILEINFO
结构体。.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)
然后是发现可执行映像中的版本资源(无大小检查)的功能。
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
VA的偏移量很容易。
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);
,并继续到下一级目录,我们不关心的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;
第三层次有资源的语言。我们不在乎要么,所以只要抓住第一个:
//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;
而且我们得到的真正的资源,嗯,其实包含了实际资源的位置和大小,但我们没有一个struct关心大小。
DWORD verVa = READ_DWORD(verDir);
这是版本资源的VA,它很容易转换为指针。
const char *verPtr = raw + (verVa - vaSec);
return verPtr;
完成!如果没有找到返回NULL
。
}
return NULL;
}
return NULL;
}
现在找到了版本资源,我们必须解析它。它实际上是一个树(还有什么)对“name”/“value”。有些值是众所周知的,这就是你正在寻找的,只是做一些测试,你会发现哪些。
注意:所有的字符串存储在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;
该节点的名称是一个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;
}
更多填充,如果neccesary:
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);
}
最后,作为奖励,一个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;
}
我已经测试了几个随机EXE,它似乎工作得很好。
太棒了!当有人问道“stackoverflow有多好?”时,这个链接将成为我的回答。 – TheCodeArtist
太棒了!谢谢。 – craig65535
我创建了一个将所有C列表组合成一个C源代码文件的要点。我已经验证它编译和工作。你可以在这里找到它:https://gist.github.com/djhaskin987/d1860a7d98193913bcfa – djhaskin987
安装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=vs.85).aspx
或任何其他功能。
我从来没有这样做过,但我会从这些发现开始。
关于exe文件在内存约束中,你可以将它复制到RAM磁盘?
此解决方案是否需要将WINE安装在正在使用该应用程序的机器上? winelib是一个静态库还是有一个静态库版本,这样可以将一个可执行文件移植到其他Linux机器上,而无需在该机器上安装WINE?这是独立于Linux发行版吗? –
@RichardChambers使用winelib编译的AFAIK应用程序需要Wine才能运行。 – PiotrNycz
如果你必须从内存中读取它,我认为你必须自己实现一些东西。 一个良好的开端是wrestool:
wrestool --type=16 -x --raw Paint.NET.3.5.10.Install.exe
它可以在Ubuntu上icoutils。 版本信息只是嵌入在可执行文件中的资源文件上的一系列字符串。我想你可以看看wrestool的源代码,并检查它们如何找到资源块的开始。找到VERSION_INFO资源后,您可以了解如何将它们转换为可打印的信息(使用十六进制编辑器,它是可读的)。
icoutils是GPL ...所以你不能只抓住它的一部分,而不是没有污染你的程序。但是源代码是免费的,所以你可以检查他们做了什么并编写自定义解决方案。
PE文件格式也可在互联网上获得。你可以从这里开始: http://msdn.microsoft.com/library/windows/hardware/gg463125
这是Tcl语言解析通过.exe文件检索版本信息的一个示例。
Reading version information from Win32 executables。
This web page describes the .exe header format。我不确定这些信息的日期,或者它是否适用于更新版本的Windows。但它是一个起点。
下面是支持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);
我知道pev
是Ubuntu的一个工具,它可以让你看到这个信息,有很多其他的PE头信息一起。我也知道它写在C. Maybe you'll want to have a look at it。从history section在文档中的位:
PEV先后诞生于2010年,从一个简单的需求:一个程序来找出PE32文件 版本(文件版本)和可以在Linux下运行。 此版本号存储在资源(.rsrc)部分,但在 时间,我们已决定在整个 二进制文件中简单搜索字符串,而不进行任何优化。
稍后我们决定解析PE32文件,直到到达.rsrc 部分并获取文件版本字段。为了做到这一点,我们 意识到,我们必须分析整个文件,我们想,如果能 打印出所有的字段和值,以及...
直到版本0.40,PEV是为解析一个独特的方案PE标头 和部分(现在readpe负责此)。在版本0.50中,我们 专注于恶意软件分析,并将其分解为各种程序 ,超出了一个名为libpe的库。目前所有的pev程序都使用libpe。
http://stackoverflow.com/questions/1291570/native-linux-app-to-edit-win32-pe-like-reshacker?rq=1 – piokuc
我不确定我是否理解EXE文件只能从内存中读取。我也不理解您希望避免将EXE文件写入磁盘的评论。您描述的各种EXE属性是资源,字符串,存储在EXE或DLL的资源部分。所以基本的机制是读取EXE或DLL文件寻找资源部分,然后解析资源部分寻找你想要的特定版本等资源并显示它们。 –
你是否在另一台机器上(从Linux到Windows)拨动正在运行的可执行文件,也许是通过火线DMA访问? – ixe013