Explorerの[バージョン]タブにリストされているWindowsEXEファイルのプロパティを返すLinuxで使用できるライブラリはありますか?これらは、製品名、製品バージョン、説明などのフィールドです。
私のプロジェクトでは、EXEファイルはファイルからではなく、メモリからのみ読み取ることができます。EXEファイルをディスクに書き込まないようにしたいと思います。
Explorerの[バージョン]タブにリストされているWindowsEXEファイルのプロパティを返すLinuxで使用できるライブラリはありますか?これらは、製品名、製品バージョン、説明などのフィールドです。
私のプロジェクトでは、EXEファイルはファイルからではなく、メモリからのみ読み取ることができます。EXEファイルをディスクに書き込まないようにしたいと思います。
ファイルのバージョンはVS_FIXEDFILEINFO
構造体にありますが、実行可能データで見つける必要があります。あなたがやりたいことをする2つの方法があります:
VS_FIXEDFILEINFO
構造体を直接読み取ります。.rsrc
、リソースツリーを解析し、リソースを見つけて解析し、データRT_VERSION
を抽出しVS_FIXEDFILEINFO
ます。最初のものは簡単ですが、間違った場所で偶然に署名を見つける可能性があります。また、他のデータ(商品名、説明など)はこの構造に含まれていないので、難しい方法でデータを取得する方法を説明しようと思います。
PE形式は少し複雑なので、コードを1つずつ、コメント付きで、最小限のエラーチェックで貼り付けています。データを標準出力にダンプする簡単な関数を作成します。適切な関数としてそれを書くことは、読者の練習問題として残されています:)
構造体フィールドの配置またはパディングに関連する移植性の問題を回避するために、構造体を直接マッピングする代わりに、バッファーでオフセットを使用することに注意してください。とにかく、使用する構造体のタイプに注釈を付けました(詳細については、インクルードファイル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;
ここに興味深い部分があります。リソースセクションを見つけたいのです。これには2つの部分があります: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とファイルへのセクションのオフセット(セクションのサイズもありますが、チェックしていません!)の2つの関連メンバーがあります。
DWORD vaSec = READ_DWORD(sec + 12);
const char *raw = buf + READ_DWORD(sec + 20);
これで、以前に取得したVAに対応するファイル内のオフセットvaRes
が簡単になりました。
const char *resSec = raw + (vaRes - vaSec);
これは、リソースデータへのポインタです。個々のリソースはすべてツリー形式で設定され、1)リソースのタイプ、2)リソースの識別子、3)リソースの言語の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;
3番目のレベルには、リソースの言語があります。私たちも気にしないので、最初のものをつかむだけです:
//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;
そして、実際のリソースに到達します。実際には、実際のリソースの場所とサイズを含む構造体ですが、サイズは関係ありません。
DWORD verVa = READ_DWORD(verDir);
これがバージョンリソースのVAであり、簡単にポインタに変換されます。
const char *verPtr = raw + (verVa - vaSec);
return verPtr;
そして完了!見つからない場合は戻りNULL
ます。
}
return NULL;
}
return NULL;
}
バージョンリソースが見つかったので、それを解析する必要があります。それは実際には「名前」/「値」のペアのツリー(他に何でも)です。いくつかの値はよく知られており、それがあなたが探しているものです。いくつかのテストを行うだけで、どれを見つけることができます。
注:すべての文字列は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;
}
必要に応じて、より多くのパディング:
offs = PAD(offs);
が0でない場合type
は、文字列バージョンデータです。
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でテストしましたが、問題なく動作するようです。
私pev
はあなたが他の多くのPEヘッダー情報と一緒にこの情報を見ることができるUbuntuのツールであることを知っています。また、Cで書かれていることも知っています。たぶんあなたはそれを見てみたいと思うでしょう。ドキュメントの履歴セクションから少し:
pevは、2010年に単純なニーズから生まれました。PE32ファイルのバージョン(ファイルバージョン)を見つけるプログラムであり、Linuxで実行できます。このバージョン番号はリソース(.rsrc)セクションに保存されていますが、現時点では、最適化せずに、バイナリ全体で文字列を検索することにしました。
その後、.rsrcセクションに到達して[ファイルバージョン]フィールドを取得するまで、PE32ファイルを解析することにしました。そのためには、ファイル全体を解析する必要があることに気づき、すべてのフィールドと値も出力できるかどうかを考えました...
バージョン0.40まで、pevはPEヘッダーとセクションを解析するための独自のプログラムでした(現在はreadpeがこれを担当しています)。バージョン0.50では、マルウェア分析に重点を置き、pevをlibpeと呼ばれるライブラリ以外のさまざまなプログラムに分割しました。現在、すべてのpevプログラムはlibpeを使用しています。
winelibをインストールしますhttp://www.winehq.org/docs/winelib-guide/index これは、Linuxを含む他のシステムへのMSWindowsAPIの移植です。
次に、MSWindowsAPIを使用します。GetFileVersionInfo
http://msdn.microsoft.com/en-us/library/windows/desktop/ms647003(v=vs.85).aspx
またはその他の関数のように。
私はそれをしたことはありませんが、私はこれらの発見から始めます。
メモリ制約内のexeファイルについて、RAMディスクにコピーできますか?
これは、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);
これは、バージョン情報を取得するために.exeファイルを解析するTcl言語の例です。
このWebページでは、.exeヘッダー形式について説明しています。この情報の日付や、最新バージョンのWindowsに適用されるかどうかはわかりません。しかし、それは出発点です。