我可以在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