_tmain関数をコーディング(2/2)
シグネチャ、ファイルヘッダ、オプショナルヘッダの各メンバをセットしてPEヘッダ部分は完了です。
//コードセクションヘッダ IMAGE_SECTION_HEADER CodeSectionHeader; memset((char *)CodeSectionHeader.Name,0,IMAGE_SIZEOF_SHORT_NAME); lstrcpy((char *)CodeSectionHeader.Name,".text"); //メモリ上のサイズ CodeSectionHeader.Misc.VirtualSize= SizeOf_CodeSection; //メモリ上の開始アドレス CodeSectionHeader.VirtualAddress= Pos_CodeSection; //ファイル上のサイズ CodeSectionHeader.SizeOfRawData= sizeof(NativeBuffer); //ファイル上の開始アドレス CodeSectionHeader.PointerToRawData= Pos_CodeSection; CodeSectionHeader.PointerToRelocations= 0; CodeSectionHeader.PointerToLinenumbers= 0; CodeSectionHeader.NumberOfRelocations= 0; CodeSectionHeader.NumberOfLinenumbers= 0; CodeSectionHeader.Characteristics= IMAGE_SCN_MEM_EXECUTE| IMAGE_SCN_MEM_READ| IMAGE_SCN_CNT_CODE; //インポートセクションヘッダ IMAGE_SECTION_HEADER ImportSectionHeader; memset((char *)ImportSectionHeader.Name,0,IMAGE_SIZEOF_SHORT_NAME); lstrcpy((char *)ImportSectionHeader.Name,".idata"); ImportSectionHeader.Misc.VirtualSize= SizeOf_ImportSection; ImportSectionHeader.VirtualAddress= Pos_ImportSection; ImportSectionHeader.SizeOfRawData= SizeOf_ImportSection; ImportSectionHeader.PointerToRawData= Pos_ImportSection; ImportSectionHeader.PointerToRelocations=0; ImportSectionHeader.PointerToLinenumbers=0; ImportSectionHeader.NumberOfRelocations= 0; ImportSectionHeader.NumberOfLinenumbers= 0; ImportSectionHeader.Characteristics= IMAGE_SCN_CNT_INITIALIZED_DATA| IMAGE_SCN_MEM_READ; //データセクションヘッダ IMAGE_SECTION_HEADER DataSectionHeader; memset((char *)DataSectionHeader.Name,0,IMAGE_SIZEOF_SHORT_NAME); lstrcpy((char *)DataSectionHeader.Name,".sdata"); DataSectionHeader.Misc.VirtualSize= SizeOf_DataSection; DataSectionHeader.VirtualAddress= Pos_DataSection; DataSectionHeader.SizeOfRawData= SizeOf_DataSection; DataSectionHeader.PointerToRawData= Pos_DataSection; DataSectionHeader.PointerToRelocations= 0; DataSectionHeader.PointerToLinenumbers= 0; DataSectionHeader.NumberOfRelocations= 0; DataSectionHeader.NumberOfLinenumbers= 0; DataSectionHeader.Characteristics= IMAGE_SCN_CNT_INITIALIZED_DATA| IMAGE_SCN_MEM_READ| IMAGE_SCN_MEM_WRITE;
今回は3つのセクションを持つEXEファイルを生成する、ということで、3つのIMAGE_SECTION_HEADER構造体を用意します。「.text」「.idata」「.sdata」はそれぞれ、コードセクション、インポートセクション、データセクションになります。
セクションヘッダでは主に、ファイル上またはメモリ上におけるセクションの開始位置およびサイズを指定します。Characteristicsメンバでそのセクションが実行可能領域であるのか、データの書き込みまたは読み込みは許されるのかなどのアクセス制限に関するフラグを指定しています。
HANDLE hFile;
DWORD dwAccessBytes;
hFile=CreateFile("c:\\test.exe",GENERIC_WRITE,0,NULL,CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,NULL);
if(hFile==INVALID_HANDLE_VALUE){
printf("ファイルの保存に失敗","エラー");
return 0;
}
//ヘッダ
int iFileOffset;
WriteFile(hFile,(void *)&ImageDosHeader,sizeof(IMAGE_DOS_HEADER),
&dwAccessBytes,NULL);
iFileOffset=dwAccessBytes;
//MS-DOSスタブは省略
//0x0100までNULLを並べる
WriteFile(hFile,szNullSpace,0x0100-iFileOffset,&dwAccessBytes,NULL);
iFileOffset += dwAccessBytes;
//PEヘッダ
WriteFile(hFile,&ImagePeHdr,sizeof(IMAGE_NT_HEADERS),
&dwAccessBytes,NULL);
iFileOffset += dwAccessBytes;
//コード セクション ヘッダ
WriteFile(hFile,&CodeSectionHeader,sizeof(IMAGE_SECTION_HEADER),
&dwAccessBytes,NULL);
iFileOffset += dwAccessBytes;
//インポート セクション ヘッダ
WriteFile(hFile,&ImportSectionHeader,sizeof(IMAGE_SECTION_HEADER),
&dwAccessBytes,NULL);
iFileOffset += dwAccessBytes;
//データ セクション ヘッダ
WriteFile(hFile,&DataSectionHeader,sizeof(IMAGE_SECTION_HEADER),
&dwAccessBytes,NULL);
iFileOffset += dwAccessBytes;
//EXE_HEADER_SIZEまでNULLを並べる
WriteFile(hFile,szNullSpace,EXE_HEADER_SIZE-iFileOffset,
&dwAccessBytes,NULL);
iFileOffset += dwAccessBytes;
//コード
WriteFile(hFile,NativeBuffer,sizeof(NativeBuffer),
&dwAccessBytes,NULL);
iFileOffset += dwAccessBytes;
//Pos_ImportSectionまでNULLを並べる
WriteFile(hFile,szNullSpace,Pos_ImportSection-iFileOffset,
&dwAccessBytes,NULL);
iFileOffset += dwAccessBytes;
//インポート ディレクトリ テーブル(Nullディレクトリ テーブルを含む)
WriteFile(hFile,ImportDesc,(USE_DLL+1)*sizeof(
IMAGE_IMPORT_DESCRIPTOR),&dwAccessBytes,NULL);
iFileOffset += dwAccessBytes;
//DLL名
WriteFile(hFile,szDllName,16,&dwAccessBytes,NULL);
iFileOffset += dwAccessBytes;
//ルックアップ テーブル
WriteFile(hFile,LookupTable,sizeof(LookupTable),&dwAccessBytes,NULL);
iFileOffset += dwAccessBytes;
//ヒント テーブル
WriteFile(hFile,HintTable,sizeof(HintTable),&dwAccessBytes,NULL);
iFileOffset += dwAccessBytes;
//インポート アドレス テーブル
WriteFile(hFile,LookupTable,sizeof(LookupTable),&dwAccessBytes,NULL);
iFileOffset += dwAccessBytes;
//Pos_DataSectionまでNULLを並べる
WriteFile(hFile,szNullSpace,Pos_DataSection-iFileOffset,
&dwAccessBytes,NULL);
iFileOffset += dwAccessBytes;
//データ テーブル
WriteFile(hFile,InitBuffer,sizeof(InitBuffer),&dwAccessBytes,NULL);
iFileOffset += dwAccessBytes;
//ファイルアラインメントを考慮
if(iFileOffset%ALIGNMENT){
WriteFile(hFile,szNullSpace,ALIGNMENT-iFileOffset%ALIGNMENT,
&dwAccessBytes,NULL);
iFileOffset += dwAccessBytes;
}
//書き込み終了
CloseHandle(hFile);
printf("EXEファイルの生成が無事に完了しました。");
return 0;
}
CreateFile関数を使って「test.exe」というファイルを生成します。保存場所は適当に決めてもらって構いません。
後はひたすら用意したデータをファイルに流し込んでいくだけです。構造体と構造体の間やセクション同士の区切りなど、要所要所でアライメントを考慮するためのNULL空間が挿入されます。
今回は、MS-DOSスタブを省略してしまったので、残念ながらMS-DOSで実行することはできません。
コンパイルしてみよう
[コンパイル]ボタンをクリックしてみましょう。エラーが出ずに無事にコンパイルが終了すれば、まずは一段落。
早速、「ExeCreator.exe」を実行してみてください。「EXEファイルの生成が無事に完了しました」というメッセージが表示されれば、EXEファイルの書き込みが正常に完了しています。
test.exeを実行してみよう
「ExeCreator.exe」を実行すると、「c:\test.exe」という実行ファイルが生成されます。これが今回のサンプルプログラムで生成した独自のEXEファイルです。16KBの小振りのEXE、ダブルクリックで実行してみて、下記のようなメッセージボックスが表示されれば成功です!

まとめ
コンパイラでもない、リンカでもない、でもEXEファイルを生成できてしまうという不思議なプログラムを書いてみました。次回は、NativeBufferの内容を噛み砕き、x86命令の形態がどのようになっているのか、機械語の核心に迫ります。

