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:
- Tìm chữ ký VERSION_INFO trong tệp và đọc trực tiếp cấu trúc
VS_FIXEDFILEINFO
.
- 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.
http://stackoverflow.com/questions/1291570/native-linux-app-to-edit-win32-pe-like-reshacker?rq=1 – piokuc
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. –
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