SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Windows実行ファイル「EXE」の謎に迫る

プログラムからEXEファイルを生成してみよう

Windows実行ファイル「EXE」の謎に迫る 第4回


_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ファイルの書き込みが正常に完了しています。

ExeCreator.exeの実行完了画面
ExeCreator.exeの実行完了画面

test.exeを実行してみよう

 「ExeCreator.exe」を実行すると、「c:\test.exe」という実行ファイルが生成されます。これが今回のサンプルプログラムで生成した独自のEXEファイルです。16KBの小振りのEXE、ダブルクリックで実行してみて、下記のようなメッセージボックスが表示されれば成功です!

test.exeの実行画面
test.exeの実行画面

まとめ

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

修正履歴

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
Windows実行ファイル「EXE」の謎に迫る連載記事一覧

もっと読む

この記事の著者

山本 大祐(ヤマモト ダイスケ)

普段はActiveBasicと周辺ツールの開発を行っています。最近は諸先輩方を見習いながら勉強中の身。AB開発日記

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/419 2006/07/12 15:02

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー