はじめに
これまで解説してきたEXEファイルにまつわる情報をもとに、オリジナルのEXE生成プログラムを作ってみましょう。
「EXEを生成するなんて、とてつもない量のコードが必要なんじゃっ!?」
このように心配される方がいるかもしれませんが、ご安心ください。前回解説した構造体の各メンバに値を指定してファイルに流しこめば、知らず知らずのうちにEXEファイルが出来上がります。
誤解を招くとマズいので初めに言っておきますが、コンパイラを作るワケではありません。コンパイラはソースコードをネイティブコード(機械語)に変換するためのツールであり、今回挑戦する内容とは異なります。今回は、あらかじめ用意する少量のネイティブコードを実行するためにEXEファイルを生成するのだということを念頭に置き、作業に取り掛かってみましょう。
使用するソフトウェア
Visual C++ 2005 Express Edition(以下、VC++)を使用してプログラミングを行います。VC++は、マイクロソフトのサイトからダウンロードできます。
なお、ソースコード内でWin32 APIを利用するため、VC++のExpress Editionを使う場合は、別途Microsoft Platform SDKをインストールしておく必要があります。
プロジェクトの作成
VC++を立ち上げたら、メニューの[ファイル]-[新規作成]-[プロジェクト]をクリックします。[新しいプロジェクト]ダイアログボックスが表示されたら、[Win32コンソール アプリケーション]を選択し、プロジェクト名を「ExeCreator」などとして[OK]ボタンをクリックします。続けてウィザードが表示されますが、[完了]ボタンをクリックしてください。
これで空のプロジェクトが作成できます。
次に、メニューの[プロジェクト]-[ExeCreatorのプロパティ]をクリックします。構成を[すべての構成]にし、[構成プロパティ]の[全般]にある[文字セット]を[マルチバイト文字セットを利用する]にセットして[OK]ボタンをクリックします。
ここからコーディングに入るのですが、編集が必要なファイルは「ExeCreator.cpp」のみです。他のファイルは一切触らずに作業を進めます。
ヘッダ部分のコーディング
// ExeCreator.cpp : コンソール アプリケーションの // エントリ ポイントを定義します。 #include "stdafx.h" #include <time.h> #include <windows.h> #include <stdlib.h> #include <malloc.h> #define ALIGNMENT 0x1000 //ファイルおよびメモリにおけるアライメント #define EXE_HEADER_SIZE 0x1000 //EXEヘッダの総合サイズ(アライメント考慮) //ネイティブコード(コードセクションに保存されるバイナリ) BYTE NativeBuffer[]={ 0x6A,0x00, //push 0 0x68, 0x0D,0x30,0x40,0x00, //push dword ptr["exetest"] 0x68, 0x00,0x30,0x40,0x00, //push dword ptr["Hello World!"] 0x6A,0x00, //push 0 0xFF,0x15, 0x4E,0x20,0x40,0x00, //call MessageBoxA 0xC3 //ret 0 }; //インポートセクションに保存されるデータ #define USE_DLL 1 IMAGE_IMPORT_DESCRIPTOR ImportDesc[USE_DLL+1]; char szDllName[16]="user32.dll"; DWORD LookupTable[2]; char HintTable[]={ 0,0, 'M','e','s','s','a','g','e','B','o','x','A',0 }; //初期済みデータ(データセクションに保存されるバイナリ) BYTE InitBuffer[]="Hello World!\0exe test\0\0"; //NULL空間 char szNullSpace[ALIGNMENT];
EXEファイルが保持する構造体はすべて「windows.h」で定義されています。特別なヘッダファイルのインクルードは必要ありません。
ALIGNMENT
定数ではアライメントを指定します。今回は、コードを簡略化するため、メモリ上・ファイル上のアライメントを同一にして計算を行います。
NativeBuffer
にはネイティブコードを記述します。この配列データが生の機械語を保持します。
この他、インポートセクション用のデータ、初期済みデータ、NULL空間用のデータを定義して先頭部分のコーディングは完了です。