2012-09-12 27 views
8

Có thư viện nào tôi có thể sử dụng trong Linux sẽ trả về các thuộc tính của tệp Windows EXE được liệt kê trong tab Phiên bản của Explorer không? Đây là các trường như Tên sản phẩm, Phiên bản sản phẩm, Mô tả, v.v.Thư viện C để đọc phiên bản EXE từ Linux?

Đối với dự án của tôi, tệp EXE chỉ có thể đọc từ bộ nhớ, không phải từ tệp. Tôi muốn tránh viết tập tin EXE vào đĩa.

+6

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

+0

Tôi không chắc chắn rằng tôi hiểu sự hạn chế của Tệp EXE chỉ có thể đọc từ bộ nhớ.Tôi cũng không hiểu nhận xét mà bạn muốn tránh viết tệp EXE vào đĩa. Các thuộc tính EXE khác nhau mà bạn mô tả là các tài nguyên, chuỗi, được lưu trữ trong phần tài nguyên của một EXE hoặc DLL. Vì vậy, cơ bản cơ bản là để đọc các tập tin EXE hoặc DLL tìm kiếm phần tài nguyên và sau đó phân tích cú pháp thông qua phần tài nguyên tìm kiếm phiên bản cụ thể, vv tài nguyên mà bạn muốn và hiển thị chúng. –

+0

Bạn có đang chạy một tệp thực thi đang chạy trên một máy khác (từ Linux đến Windows), có thể thông qua truy cập DMA firewire không? – ixe013

Trả lời

21

Phiên bản của tệp có cấu trúc VS_FIXEDFILEINFO, nhưng bạn phải tìm nó trong dữ liệu thực thi. Có hai cách để thực hiện những gì bạn muốn:

  1. Tìm chữ ký VERSION_INFO trong tệp và đọc trực tiếp cấu trúc VS_FIXEDFILEINFO.
  2. Tìm phần .rsrc, phân tích cú pháp cây tài nguyên, tìm tài nguyên RT_VERSION, phân tích cú pháp và trích xuất dữ liệu VS_FIXEDFILEINFO.

Cách thứ nhất dễ dàng hơn, nhưng dễ bị tìm thấy chữ ký do nhầm lẫn. Hơn nữa, các dữ liệu khác mà bạn yêu cầu (tên sản phẩm, mô tả, vv) không có trong cấu trúc này, vì vậy tôi sẽ cố gắng giải thích cách lấy dữ liệu theo cách cứng.

Định dạng PE hơi phức tạp nên tôi dán đoạn mã theo từng mảnh, với nhận xét và kiểm tra lỗi tối thiểu. Tôi sẽ viết một hàm đơn giản để đưa dữ liệu vào đầu ra tiêu chuẩn. Viết nó như là một chức năng thích hợp còn lại như một bài tập cho người đọc :)

Lưu ý rằng tôi sẽ sử dụng bù đắp trong bộ đệm thay vì ánh xạ trực tiếp các cấu trúc để tránh các vấn đề về di chuyển liên quan đến căn chỉnh hoặc đệm của các trường struct . Dù sao, tôi đã chú thích loại cấu trúc được sử dụng (xem tập tin winnt.h để biết chi tiết).

Lần đầu tiên một vài tờ khai hữu ích, họ nên được tự giải thích:

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) 

Sau đó, một chức năng mà tìm thấy tài nguyên Version trong hình ảnh thực thi (không có kiểm tra kích thước).

const char *FindVersion(const char *buf) 
{ 

Cấu trúc đầu tiên trong EXE là tiêu đề MZ (để tương thích với MS-DOS).

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

Trường duy nhất thú vị trong tiêu đề MZ là độ lệch của tiêu đề PE. Tiêu đề PE là điều thực sự.

//pe is a IMAGE_NT_HEADERS32 
    const char *pe = buf + READ_DWORD(buf + 0x3C); 
    if (READ_WORD(pe) != 0x4550) //PE signature 
     return NULL; 

Thực tế, tiêu đề PE khá nhàm chán, chúng tôi muốn tiêu đề COFF có tất cả dữ liệu biểu tượng.

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

Chúng tôi chỉ cần các trường sau đây từ trường này.

WORD numSections = READ_WORD(coff + 2); 
    WORD optHeaderSize = READ_WORD(coff + 16); 
    if (numSections == 0 || optHeaderSize == 0) 
     return NULL; 

Tiêu đề tùy chọn thực sự bắt buộc trong EXE và nó chỉ sau COFF. Sự kỳ diệu là khác nhau cho 32 và 64 bit Windows. Tôi giả sử 32 bit từ đây.

//optHeader is a IMAGE_OPTIONAL_HEADER32 
    const char *optHeader = coff + 20; 
    if (READ_WORD(optHeader) != 0x10b) //Optional header magic (32 bits) 
     return NULL; 

Đây là phần thú vị: chúng tôi muốn tìm phần tài nguyên. Nó có hai phần: 1. dữ liệu phần, 2. siêu dữ liệu phần.

Vị trí dữ liệu nằm trong bảng ở cuối tiêu đề tùy chọn và mỗi phần có chỉ mục nổi tiếng trong bảng này. phần tài nguyên là chỉ số 2, vì vậy chúng tôi có được địa chỉ ảo (VA) của bộ phận tài nguyên với:

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

Để có được siêu dữ liệu phần chúng ta cần phải lặp bảng phần tìm kiếm một phần tên .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; 

Phần struct có hai thành viên có liên quan: VA của phần này và bù đắp của phần này vào tập tin (cũng là kích thước của phần này, nhưng tôi không kiểm tra nó!):

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

Bây giờ, bù đắp trong tệp tương ứng với vaRes VA mà chúng tôi nhận được trước đây thật dễ dàng.

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

Đây là con trỏ đến dữ liệu tài nguyên.Tất cả các tài nguyên cá nhân được thiết lập dưới dạng cây, với 3 cấp độ: 1) loại tài nguyên, 2) định danh tài nguyên, 3) ngôn ngữ của tài nguyên. Đối với phiên bản, chúng tôi sẽ nhận được loại đầu tiên của loại chính xác.

Thứ nhất, chúng tôi có một thư mục tài nguyên (đối với loại tài nguyên), chúng tôi nhận số lượng các mục trong thư mục, cả hai được đặt tên và giấu tên và lặp:

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

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

Đối với mỗi mục tài nguyên chúng tôi nhận được loại tài nguyên và loại bỏ nó nếu nó không phải là hằng số 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; 

Nếu nó là một RT_VERSION chúng tôi nhận được vào thư mục tài nguyên tiếp theo trong cây:

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

Và đi đến cấp độ thư mục tiếp theo, chúng tôi không quan tâm đến các id. của cái này:

  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; 

Cấp thứ ba có ngôn ngữ của tài nguyên. Chúng tôi không quan tâm, hoặc, vì vậy chỉ cần lấy đầu tiên:

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

Và chúng tôi đến được với các nguồn lực thực sự, tốt, thực sự là một struct có chứa các vị trí và kích thước của tài nguyên thật, nhưng chúng tôi không quan tâm đến kích thước.

  DWORD verVa = READ_DWORD(verDir); 

Đó là phiên bản VA của phiên bản được chuyển đổi thành con trỏ dễ dàng.

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

Và hoàn tất! Nếu không tìm thấy trả lại NULL.

 } 
     return NULL; 
    } 
    return NULL; 
} 

Bây giờ tài nguyên phiên bản được tìm thấy, chúng tôi phải phân tích cú pháp. Nó thực sự là một cái cây (cái gì khác) của cặp "tên"/"giá trị". Một số giá trị nổi tiếng và đó là những gì bạn đang tìm kiếm, chỉ cần làm một số thử nghiệm và bạn sẽ tìm ra cái nào.

LƯU Ý: Tất cả các chuỗi được lưu trữ trong UNICODE (UTF-16) nhưng mã mẫu của tôi thực hiện chuyển đổi câm thành ASCII. Ngoài ra, không có kiểm tra cho tràn.

Hàm này lấy con trỏ đến tài nguyên phiên bản và bù đắp vào bộ nhớ này (0 cho người mới bắt đầu) và trả về số byte được phân tích.

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

Đầu tiên của tất cả các bù đắp phải là một bội số của 4.

offs = PAD(offs); 

Sau đó chúng tôi nhận được các thuộc tính của nút phiên bản cây.

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

Tên của nút là chuỗi Unicode không bị chấm dứt.

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

More đệm, Nếu cần:

offs = PAD(offs); 

Nếu type không phải là 0, sau đó nó là một dữ liệu chuỗi phiên bản.

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

khác, nếu tên là VS_VERSION_INFO sau đó nó là một cấu trúc VS_FIXEDFILEINFO. Khác nó là dữ liệu nhị phân.

else 
    { 
     if (strcmp(info, "VS_VERSION_INFO") == 0) 
     { 

Tôi chỉ đang in phiên bản của tệp và sản phẩm, nhưng bạn có thể dễ dàng tìm thấy các trường khác của cấu trúc này. Cẩn thận với hỗn hợp endian đặt hàng.

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

Bây giờ thực hiện cuộc gọi đệ quy để in toàn bộ cây.

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

Và một số phần đệm khác trước khi quay lại.

return PAD(offs); 
} 

Cuối cùng, làm tiền thưởng, chức năng 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; 
} 

Tôi đã thử nghiệm nó với một vài EXE ngẫu nhiên và có vẻ như nó hoạt động tốt.

+0

Câu trả lời thú vị! Liên kết này sẽ là câu trả lời của tôi khi có ai đó hỏi "Stackoverflow tốt như thế nào?" – TheCodeArtist

+0

Tuyệt vời! Cảm ơn bạn. – craig65535

+0

Tôi đã tạo một danh sách kết hợp tất cả các danh sách C thành một tệp mã nguồn C duy nhất. Tôi đã xác minh nó biên dịch và hoạt động. Bạn có thể tìm thấy ở đây: https://gist.github.com/djhaskin987/d1860a7d98193913bcfa – djhaskin987

1

Cài đặt winelib http://www.winehq.org/docs/winelib-guide/index Đây là cổng của MS Windows API cho các hệ thống khác, bao gồm cả linux.

Sau đó, sử dụng API MS Windows. Giống như GetFileVersionInfo http://msdn.microsoft.com/en-us/library/windows/desktop/ms647003(v=vs.85).aspx
Hoặc bất kỳ chức năng nào khác.

Tôi chưa bao giờ làm điều đó, nhưng tôi sẽ bắt đầu với những phát hiện này.

Về tệp exe trong ràng buộc bộ nhớ, bạn có thể sao chép tệp đó vào đĩa RAM không?

+0

Giải pháp này có yêu cầu phải cài đặt WINE trên máy mà ứng dụng đang được sử dụng không? Là winelib một thư viện tĩnh hoặc có một phiên bản thư viện tĩnh để một thực thi có thể được chuyển đến các máy Linux khác mà không cần cài đặt WINE trên máy đó? Điều này có độc lập với bản phân phối Linux không? –

+0

@RichardChambers Ứng dụng AFAIK được biên dịch với winelib yêu cầu Wine chạy. – PiotrNycz

0

Nếu bạn phải đọc nó từ bộ nhớ, tôi nghĩ bạn phải tự mình thực hiện điều gì đó. Một khởi đầu tốt đẹp là wrestool:

wrestool --type=16 -x --raw Paint.NET.3.5.10.Install.exe 

Nó có sẵn trên Ubuntu trên icoutils. Thông tin phiên bản chỉ là một chuỗi các chuỗi trên tệp tài nguyên được nhúng trên tệp thực thi. Tôi nghĩ rằng bạn có thể có một cái nhìn về mã nguồn của wrestool và kiểm tra cách họ tìm thấy sự khởi đầu của khối tài nguyên. Khi bạn tìm thấy tài nguyên VERSION_INFO, bạn có thể tìm ra cách dịch chúng sang thông tin có thể in được (sử dụng trình chỉnh sửa hex, có thể đọc được).

icoutils là GPL ... vì vậy bạn không thể chỉ lấy một phần của nó, không phải không làm ô nhiễm chương trình của bạn. Nhưng mã nguồn là miễn phí, vì vậy bạn có thể kiểm tra những gì họ đã làm và viết một giải pháp tùy chỉnh.

Định dạng tệp PE cũng có sẵn trên internet. Bạn có thể bắt đầu tại đây: http://msdn.microsoft.com/library/windows/hardware/gg463125

+0

Tiêu đề và tài nguyên PE khác nhau và khá nhiều không liên quan, tôi sợ ... – ixe013

+1

Vâng, chúng là hai thứ khác nhau. Tiêu đề PE, của tệp thi hành, chứa nhiều phần. Một trong những phần này là một phần tài nguyên. Nếu bạn tìm thấy phần bắt đầu của phần tài nguyên, thì định dạng của tệp tài nguyên được nhúng là quan trọng. Họ đã làm điều đó trên wrestool để trích xuất các biểu tượng và chuỗi từ thực thi Windows. Thông tin phiên bản bạn muốn, cũng có. – nmenezes

1

Đây là bản vá cho mã hỗ trợ PE32 +. Thử nghiệm trên một số tệp và dường như hoạt động.

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

Tôi biết pev là công cụ trên Ubuntu cho phép bạn xem thông tin này, cùng với nhiều thông tin tiêu đề PE khác. Tôi cũng biết nó được viết bằng C. Maybe you'll want to have a look at it. Một chút từ history section của nó trong tài liệu:

pev đã sinh ra trong năm 2010 từ một nhu cầu đơn giản: một chương trình để tìm ra phiên bản (File Version) của một tập tin PE32 và có thể được chạy trong Linux. Số phiên bản này được lưu trong phần Tài nguyên (.rsrc) nhưng tại thời điểm , chúng tôi đã quyết định chỉ tìm kiếm chuỗi trong toàn bộ mã nhị phân , mà không có bất kỳ tối ưu hóa nào.

Sau đó, chúng tôi đã quyết định phân tích cú pháp tệp PE32 cho đến khi đạt được phần .rsrc và nhận trường Phiên bản tệp. Để làm được điều đó, chúng tôi nhận ra rằng chúng tôi phải phân tích cú pháp toàn bộ tệp và chúng tôi nghĩ rằng nếu chúng tôi có thể in ra tất cả các trường và giá trị ...

10 Cho đến phiên bản 0.40, pev là một chương trình độc đáo để phân tích cú pháp Các tiêu đề PE và các phần (bây giờ readpe chịu trách nhiệm về điều này). Trong phiên bản 0.50, chúng tôi tập trung vào phân tích phần mềm độc hại và chia nhỏ pev thành các chương trình khác nhau ngoài thư viện, được gọi là libpe. Hiện tại tất cả các chương trình pev đều sử dụng libpe.