七誌の開発Wiki - PE勉強会ネタ帳
第1回 http://atnd.org/events/11721
第2回 http://atnd.org/events/12049

環境チェック (13:10〜13:20)


gcc, テキストエディタ, バイナリエディタ, OOo/Excel

16進数 (13:20〜13:30)


今回は2進数を使わないので、説明は省略。
  • 16進数の数え方
  • 練習として、1〜20まで紙に書いて数えてもらう。
  • 変換にはOOo/Excelでの方法を説明。Excelでは統計アドオンを有効化。

バイナリエディタ (13:30〜13:40)

  • 適当にファイルを開いて、16進数の羅列になっていることを確認。
  • アドレスの求め方を説明。横に16バイト並んでいるのを縦1列に並べて説明。
  • ファイル形式によって数字の並びにルールがあるという説明。

gcc (13:40〜13:50)

  1. MSYSのホームの場所をマイコンピュータから開く。
  2. テキストエディタでC言語のhello worldを書いてホームに保存。各自好きなエディタで。
  3. MSYSを起動してコンパイルと実行方法を説明。エクスプローラから実行するとすぐ終了することも補足。
  4. 出力したhello worldをバイナリエディタで開く。文字列が埋め込まれている等を確認。

エンディアンと整数型 (14:00〜14:10)

  1. グローバル変数に数字を入れてコンパイルして、バイナリエディタで確認。あとで.dataセクションの説明をするのに都合が良いので、ローカル変数ではなくグローバル変数を使う。
  2. 巨大数(0x12345678等)を入れてみて、バイナリで順序が逆になっていることを確認。
  3. 逆転をリトルエンディアンと呼ぶということを説明。
  4. 型とバイト長の関係を説明。char/short/int
  5. いくつかコンパイルして確認してもらう。

DOSスタブ (14:10〜14:30)

  1. DOSのEXEが前にくっ付いていることを説明。バイナリエディタで確認。
  2. 32bit Windowsの人にはコマンドプロンプトからdebugを起動して16bit EXEの動作を確認。
  3. 64bit Windowsについては、ロングモードと仮想86モードのことに軽く触れる。MS-DOS Player for Win32-x64も紹介。
  4. DOSスタブ以降をばっさり切ってみる実験。どちらかというと内部構造の理解より、バイナリエディタに慣れるのが目的。
    1. 003c: 80 → 00
    2. 0002: 90 → 00
    3. 0004: 03 → 01
    4. 0080以降をカット。BZでの操作法として0080をクリック、最後までスクロール、[Shift]+クリック、[Delete]を説明。
    5. これで動くことを確認。
  5. テキスト部分を直接編集して、表示メッセージを変更してみる。16進数と文字コードについても説明する。
  6. 時間があればEXEを削ってCOMを作ってみる。開始アドレスが0100のためポインタを変更するが、まだアセンブリ言語について説明していないので、その部分は説明しない。

ヘッダの確認 (14:30〜14:50)

  1. 数字の羅列の部分にも、一定のルールがあることを説明。
  2. MinGWヘッダのwinnt.hのIMAGE_FILE_HEADER構造体を紹介。
  3. Win32特有のtypedef型名を説明。BYTE/WORD/DWORD
  4. バイナリエディタを開いて、IMAGE_FILE_HEADERヘッダの位置から読み取ってもらう。後でマクロに応用するため、テキストエディタではなくOOo/Excelを推奨。
  5. 他のヘッダも同様に解析。セクションリストまで得る。

objdump (15:20〜15:30)

  1. 解析ツールがあることに言及。とりあえずMinGWに付いて来るのでobjdumpを使う。
  2. objdump -xを説明。ファイルとして残しておくためリダイレクトを説明。
  3. OOo/Excelの結果と比較してみる。

メモリ (15:30〜15:35)

  • セクションを説明する前にメモリの実態がどんなものか軽く説明。
  • アドレスとデータの関係を、バイナリエディタとの連想でイメージしてもらう。先にバイナリエディタを持ち出したのはこの導入のため。
  • 32bitでのアドレスは16進数8桁で、4GBということを説明。
  • 仮想アドレスには言及しない。また回を改めて。

セクション (15:35〜15:50)

  1. メモリごとにブロックがあることを説明。
  2. ファイル中の位置、メモリ中の位置、相対アドレスの関係について説明。
  3. 原理を理解する程度の簡単な計算練習をした後、OOo/Excelに数式を入れて自動計算。
  4. stripコマンドでセクションを絞る。デバッグ用で実行には直接関係ないと説明。
  5. .textと.dataを説明。
  6. エンディアンの説明のときに見た初期値が.dataに入っていることを確認。
  7. 文字列が.rdataに入っていることを確認。OSが書込み禁止にしていることを簡単に説明。
  8. グローバル変数のポインタや関数ポインタを取得して、PEで定義されたアドレスと合致するか確認する。

逆アセンブル (16:00〜16:10)

  • バイナリエディタと比較するにはgcc -Sよりobjdump -dの方が都合が良い。
  • AT&T形式は軽く流して、さらっと-Mintelをつけて説明。
  • ファイルに残すため再度リダイレクトにも言及する。
  • objdumpの結果とバイナリエディタとを見比べる。
  • ソース混在形式を示すため、gcc -gとobjdump -Sを説明。
  • ソースに書いていないのに付いている部分に関して、CRTについて簡単に説明。
  • gcc -gでコンパイルするとデバッグ情報が付加されてファイルサイズが膨らむ。バイナリエディタで見るのに適さないので、stripについても言及する。
  • gcc/gcc -g/strip後のバイナリの関係について、簡単な図で説明する。いきなりstrip後のバイナリを出す方法としてgcc -sを説明する。
  • コンパイラの勉強会ではないので、gcc -Sとの役割の比較にまでは言及しない。
  • オプションがいくつか出てきたので表に整理する。

バイナリエディタでPE作成 (16:10〜16:40)

  • hello worldのEXEをOOo/Excelで分析したものを見ながら、不必要なものを削っていく。具体的には.textセクション以外は全部消す。
  • 編集したスプレッドシートを見ながら、ハローワールドのEXEを改変していく。
    • 改変箇所の明示のため、スプレッドシートで色を付けた。
    • UNIXに慣れている人はテキストファイルのdiffでも構わない。
  • .textにはC3(ret)だけ書く。ハンドアセンブルというほどでもない。
  • 実行して何もせずに終了することを確認。
  • objdump -dで確認。

インポートセクション (16:40〜16:50)

  • Win32APIを使うにはDLLをインポートする必要がある。インポートセクションについて簡単に説明する。
  • やや複雑。この部分を表にまとめるには、スプレッドシートが有効だと思う。

手書きハローワールド (17:00〜17:30)

  • .dataと.idataを付加。
  • OOo/Excelでインポートセクションを下書きした後、バイナリエディタで書き込み。
  • 先ほどの手書きバイナリにインポートセクションを付けて、MessageBoxでハローワールドしたい。
  • ハンドアセンブルでMessageBoxを呼び出してみる。
    • この程度だとアセンブラを使うよりも、バイナリエディタでハンドアセンブルした方が直接的で分かりやすいと思う。
  • MessageBoxを呼び出すため、push/mov/call/retを説明。movは.idataからMessageBoxのアドレスを取得するのに使用。
    • C言語との対応を簡単に説明。stdcallまでは説明しない。
  • 文字列の手打ちは、BZなら右欄のASCIIダンプから書き込めるので簡単。
  • これでちゃんと起動したら、まさに「こんにちは、世界」という感じ。

フォーマットの説明 (時間が余れば)


今回は実習中心にしたかったため、歴史的な説明などは最初にやらなかった。時間があれば最後に説明する。
  • COFFについて言及。
    • UNIXでは a.out → COFF → ELF
    • WindowsではELFは使っていない。DOSのEXEにCOFFを付け足して拡張したPEとして派生。
    • PEではない純粋なCOFFは、WindowsではMIPSやAlphaのARCファームウェアから呼び出すARCINST.exeで使われていた。DOSスタブやPEシグナチャなしで、いきなりヘッダから始まっている。
  • PE32+やCLIヘッダについて簡単に説明。
    • PE32+は64bit用。アドレスなどが64bitになっている。
    • CLIヘッダは.NET用。.NETではCIL(バイトコード)をPEの中に入れるが、そのための特殊なセクションを定義している。

資料 (17:30〜17:35)


PEの仕様書を紹介。最初に渡すと確認のために余計時間が掛かるので、わざと後出し。今回の範囲では特に確認するほどのことはやらない。

PEを出力する簡単な自作サンプル。

次回予告 (17:35〜17:40)


とりあえず1回やってみて、反響を見ながら内容を修正して、次の週に修正版で第2回をやってから、次の内容に進む予定。

  • 第3回: 簡単なコンパイラを作る。OOo/Excelでやるときつい感じなので、例題をC#で提示して、各自好きな言語で実装。プログラミング未経験者にはC#のフォローはする。落ちたらOllyDbgを使って調べる方法も説明。
  • 第4回: DLLのエクスポートと再配置について説明。前回のコンパイラを拡張してDLLを作成。作ったDLLを自作ローダで読み込むところまでできれば面白い。外部DLLに依存していなければ他OSでも読み込めて面白い。

理屈として再配置はコンパイラを作らなくてもできるけど、ある程度慣れてからでないと混乱すると思う。