Versi file ada di VS_FIXEDFILEINFO
struct, tetapi Anda harus menemukannya ke dalam data yang dapat dieksekusi. Ada dua cara untuk melakukan apa yang Anda inginkan:
- Telusuri tanda tangan VERSION_INFO di file dan baca
VS_FIXEDFILEINFO
struct secara langsung. - Temukan
.rsrc
bagian, parsing pohon sumber daya, cariRT_VERSION
sumber daya, uraikan dan ekstrakVS_FIXEDFILEINFO
data.
Yang pertama lebih mudah, tetapi rentan untuk menemukan tanda tangannya secara kebetulan di tempat yang salah. Selain itu, data lain yang Anda minta (nama produk, deskripsi, dll.) tidak ada dalam struktur ini, jadi saya akan mencoba menjelaskan cara mendapatkan data tersebut dengan cara yang sulit.
Format PE agak berbelit-belit, jadi saya menempelkan kodenya sepotong demi sepotong, dengan komentar, dan dengan pemeriksaan kesalahan minimum. Saya akan menulis fungsi sederhana yang membuang data ke keluaran standar. Menulisnya sebagai fungsi yang tepat diserahkan sebagai latihan kepada pembaca :)
Perhatikan bahwa saya akan menggunakan offset dalam buffer alih-alih memetakan struct secara langsung untuk menghindari masalah portabilitas yang terkait dengan penyelarasan atau pengisian bidang struct. Lagi pula, saya telah memberi anotasi jenis struct yang digunakan (lihat sertakan file winnt.h untuk detailnya).
Pertama beberapa deklarasi yang berguna, mereka harus cukup jelas:
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)
Kemudian fungsi yang menemukan sumber Versi dalam gambar yang dapat dieksekusi (tidak ada pemeriksaan ukuran).
const char *FindVersion(const char *buf)
{
Struktur pertama di EXE adalah header MZ (untuk kompatibilitas dengan MS-DOS).
//buf is a IMAGE_DOS_HEADER
if (READ_WORD(buf) != 0x5A4D) //MZ signature
return NULL;
Satu-satunya bidang yang menarik di header MZ adalah offset header PE. Header PE adalah hal yang nyata.
//pe is a IMAGE_NT_HEADERS32
const char *pe = buf + READ_DWORD(buf + 0x3C);
if (READ_WORD(pe) != 0x4550) //PE signature
return NULL;
Sebenarnya, header PE cukup membosankan, kami ingin header COFF, yang memiliki semua data simbolik.
//coff is a IMAGE_FILE_HEADER
const char *coff = pe + 4;
Kami hanya membutuhkan bidang berikut dari bidang ini.
WORD numSections = READ_WORD(coff + 2);
WORD optHeaderSize = READ_WORD(coff + 16);
if (numSections == 0 || optHeaderSize == 0)
return NULL;
Header opsional sebenarnya wajib dalam EXE dan tepat setelah COFF. Keajaiban berbeda untuk Windows 32 dan 64 bit. Saya mengasumsikan 32 bit mulai dari sini.
//optHeader is a IMAGE_OPTIONAL_HEADER32
const char *optHeader = coff + 20;
if (READ_WORD(optHeader) != 0x10b) //Optional header magic (32 bits)
return NULL;
Inilah bagian yang menarik:kami ingin menemukan bagian sumber daya. Ini memiliki dua bagian:1. data bagian, 2. metadata bagian.
Lokasi data ada di tabel di akhir header opsional, dan setiap bagian memiliki indeks terkenal di tabel ini. Bagian sumber daya ada di indeks 2, jadi kami memperoleh alamat virtual (VA) dari bagian sumber daya dengan:
//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;
Untuk mendapatkan metadata bagian, kita perlu mengulangi tabel bagian untuk mencari bagian bernama .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;
Struktur bagian memiliki dua anggota yang relevan:VA bagian dan offset bagian ke dalam file (juga ukuran bagian, tapi saya tidak memeriksanya!):
DWORD vaSec = READ_DWORD(sec + 12);
const char *raw = buf + READ_DWORD(sec + 20);
Sekarang offset dalam file yang sesuai dengan vaRes
VA yang kami dapatkan sebelumnya mudah.
const char *resSec = raw + (vaRes - vaSec);
Ini adalah penunjuk ke data sumber daya. Semua sumber daya individu diatur dalam bentuk pohon, dengan 3 level:1) jenis sumber daya, 2) pengidentifikasi sumber daya, 3) bahasa sumber daya. Untuk versi kami akan mendapatkan yang pertama dari jenis yang benar.
Pertama, kami memiliki direktori sumber daya (untuk jenis sumber daya), kami mendapatkan jumlah entri dalam direktori, baik yang bernama maupun yang tidak disebutkan namanya dan iterasi:
WORD numNamed = READ_WORD(resSec + 12);
WORD numId = READ_WORD(resSec + 14);
int j;
for (j = 0; j < numNamed + numId; ++j)
{
Untuk setiap entri sumber daya, kami mendapatkan jenis sumber daya dan membuangnya jika bukan konstanta 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;
Jika ini adalah RT_VERSION, kita masuk ke direktori sumber daya berikutnya di pohon:
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);
Dan lanjutkan ke level direktori berikutnya, kami tidak peduli dengan id. yang satu ini:
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;
Tingkat ketiga memiliki bahasa sumber daya. Kami juga tidak peduli, jadi ambil saja yang pertama:
//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;
Dan kita mendapatkan sumber daya sebenarnya, sebenarnya sebuah struct yang berisi lokasi dan ukuran sumber daya sebenarnya, tetapi kita tidak peduli dengan ukurannya.
DWORD verVa = READ_DWORD(verDir);
Itulah VA dari sumber daya versi, yang diubah menjadi penunjuk dengan mudah.
const char *verPtr = raw + (verVa - vaSec);
return verPtr;
Dan selesai! Jika tidak ditemukan kembalikan NULL
.
}
return NULL;
}
return NULL;
}
Sekarang setelah resource versi ditemukan, kita harus menguraikannya. Ini sebenarnya adalah pohon (apa lagi) dari pasangan "nama" / "nilai". Beberapa nilai terkenal dan itulah yang Anda cari, lakukan beberapa tes dan Anda akan menemukan yang mana.
CATATAN :Semua string disimpan dalam UNICODE (UTF-16) tetapi kode sampel saya melakukan konversi bodoh menjadi ASCII. Juga, tidak ada pemeriksaan untuk luapan.
Fungsi mengambil penunjuk ke sumber daya versi dan offset ke dalam memori ini (0 sebagai permulaan) dan mengembalikan jumlah byte yang dianalisis.
int PrintVersion(const char *version, int offs)
{
Pertama-tama offset harus kelipatan 4.
offs = PAD(offs);
Kemudian kita mendapatkan properti dari simpul pohon versi.
WORD len = READ_WORD(version + offs);
offs += 2;
WORD valLen = READ_WORD(version + offs);
offs += 2;
WORD type = READ_WORD(version + offs);
offs += 2;
Nama node adalah string Unicode yang diakhiri dengan nol.
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;
}
Padding tambahan, jika perlu:
offs = PAD(offs);
Jika type
bukan 0, maka ini adalah data versi string.
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);
}
Lain, jika namanya adalah VS_VERSION_INFO
maka itu adalah VS_FIXEDFILEINFO
struct. Kalau tidak, itu adalah data biner.
else
{
if (strcmp(info, "VS_VERSION_INFO") == 0)
{
Saya hanya mencetak versi file dan produk, tetapi Anda dapat menemukan bidang lain dari struct ini dengan mudah. Waspadalah terhadap endian campuran pesanan.
//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;
}
Sekarang lakukan panggilan rekursif untuk mencetak pohon penuh.
while (offs < len)
offs = PrintVersion(version, offs);
Dan beberapa padding lagi sebelum kembali.
return PAD(offs);
}
Terakhir, sebagai bonus, sebuah main
fungsi.
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;
}
Saya telah mengujinya dengan beberapa EXE acak dan tampaknya berfungsi dengan baik.
Saya tahu pev
adalah alat di Ubuntu yang memungkinkan Anda melihat informasi ini, bersama dengan banyak info header PE lainnya. Saya juga tahu itu ditulis dalam C. Mungkin Anda ingin melihatnya. Sedikit dari bagian riwayatnya di dokumen:
pev lahir pada tahun 2010 dari kebutuhan sederhana:sebuah program untuk mengetahui versi (Versi File) dari file PE32 dan yang dapat dijalankan di Linux. Nomor versi ini disimpan di bagian Sumber Daya (.rsrc) tetapi saat ini kami telah memutuskan untuk hanya mencari string di seluruh biner, tanpa pengoptimalan apa pun.
Kemudian kami memutuskan untuk mem-parsing file PE32 hingga mencapai .rsrcsection dan mendapatkan kolom Versi File. Untuk melakukannya, kami menyadari bahwa kami harus mem-parsing seluruh file dan berpikir jika kami dapat mencetak semua bidang dan nilai juga...
Hingga versi 0.40, pev adalah program unik untuk mengurai bagian header dan PE (sekarang readpe bertanggung jawab untuk ini). Di versi 0.50 kami berfokus pada analisis malware dan membagi pev menjadi berbagai program di luar perpustakaan, yang disebut libpe. Saat ini semua program pev menggunakan libpe.