2012-09-12 45 views
8

我可以在Linux中使用哪個庫,它將返回Explorer版本選項卡中列出的Windows EXE文件的屬性?這些是像產品名稱,產品版本,說明等字段。C庫從Linux讀取EXE版本?

對於我的項目,EXE文件只能從內存中讀取,而不能從文件中讀取。我想避免將EXE文件寫入磁盤。

+6

http://stackoverflow.com/questions/1291570/native-linux-app-to-edit-win32-pe-like-reshacker?rq=1 – piokuc

+0

我不確定我是否理解EXE文件只能從內存中讀取。我也不理解您希望避免將EXE文件寫入磁盤的評論。您描述的各種EXE屬性是資源,字符串,存儲在EXE或DLL的資源部分。所以基本的機制是讀取EXE或DLL文件尋找資源部分,然後解析資源部分尋找你想要的特定版本等資源並顯示它們。 –

+0

你是否在另一臺機器上(從Linux到Windows)撥動正在運行的可執行文件,也許是通過火線DMA訪問? – ixe013

回答

21

該文件的版本位於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) 

然後是發現可執行映像中的版本資源(無大小檢查)的功能。

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,它似乎工作得很好。

+0

太棒了!當有人問道「stackoverflow有多好?」時,這個鏈接將成爲我的回答。 – TheCodeArtist

+0

太棒了!謝謝。 – craig65535

+0

我創建了一個將所有C列表組合成一個C源代碼文件的要點。我已經驗證它編譯和工作。你可以在這裏找到它:https://gist.github.com/djhaskin987/d1860a7d98193913bcfa – djhaskin987

1

安裝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磁盤?

+0

此解決方案是否需要將WINE安裝在正在使用該應用程序的機器上? winelib是一個靜態庫還是有一個靜態庫版本,這樣可以將一個可執行文件移植到其他Linux機器上,而無需在該機器上安裝WINE?這是獨立於Linux發行版嗎? –

+0

@RichardChambers使用winelib編譯的AFAIK應用程序需要Wine才能運行。 – PiotrNycz

0

如果你必須從內存中讀取它,我認爲你必須自己實現一些東西。 一個良好的開端是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

+0

PE頭和資源是不同的,幾乎沒有發現,恐怕... – ixe013

+1

是的,他們是兩回事。可執行文件的PE頭包含很多部分。其中一個部分是資源之一。如果找到資源部分的開頭,那麼嵌入資源文件的格式非常重要。他們已經在wrestool上從Windows可執行文件中提取圖標和字符串。你想要的版本信息也在那裏。 – nmenezes

1

下面是支持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); 
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。