Windowsの実行ファイル形式(5)

しかし、完全64bit対応というのも難しいものです。32bit値でもいいのにわざわざ64bitをつかうと32bit環境で速度が低下しますし、

だからといって32bit値を使おうとするとその境界で型キャストが何回もいるのでコンパイルワーニングがうるさいです。

で、スクリプトを使うために仮想マシンを組もうとするとどちらかで決めないとセーブ、ロード動作がおかしくなるので決めうちしますが、

そのときのキャスト処理がとんでもないことになるのが・・・。

今回はIMAGE_SECTION_HEADERを扱います。これはIMAGE_NT_HEADERSの後ろについているものです。

プログラムには「セクション」という概念があります。これは同じ役割を持つデータを集めて一つの仮想メモリの領域を考え(それをセクションとして扱い)、

それらの集合体としてプログラムを考えるという発想によるものです。

この概念を使うことで、それぞれのメモリ領域のメモリ保護を簡単に行うことができることと、データを扱いやすくなるという利点があります。

セクションの数は前に出てきたIMAGE_FILE_HEADERNumberOfSectionsメンバで示されます。

この数だけIMAGE_NT_HEADERSの後ろにIMAGE_SECTION_HEADER構造体が配列として存在するというメモリ状態になります。

実行ファイルを操作するときには必ずと言っていいほど操作を行う領域なのでちょっとみてみましょう。

  • Name

セクション名を格納する領域です。通常は8byteの領域です。

コンパイラやセクションを構築するプログラムによって自由につけてもいい領域ですが、何かはわかるように書くのが一般的です。たとえば

.text VisualC++系で構築したときのコード領域
CODE BorlandC系で構築したときのコード領域
UPX1 UPXの自己圧縮を使ったときの自己解凍コード領域

のような感じです。

  • VirtualSize

この領域はunionになっていますが、通常はこちらしか使いません。

対象セクションを仮想メモリ上に展開したときのセクションのメモリサイズを示します。

  • VirtualAddress

対象セクションを仮想メモリ上に展開したときの開始アドレスをロードメモリ相対で示します。

  • SizeOfRawData

ファイル上にある仮想メモリにロードされるべきデータのサイズを示します。

ファイルからこのサイズだけ読み取られて仮想メモリ上に展開されます。

未初期化データセクション(仮想メモリだけを確保するセクション)であるときは0になります。

  • PointerToRawData

ファイル上にある仮想メモリにロードされるべきデータのファイル上での先頭位置を示します。

未初期化データセクションであるときは0になります。

  • Characteristics

このセクションの「初期仮想メモリ確保時」の種別フラグを示します。

実行中にセクションのメモリ保護状態などが変更してもこのフラグを変更する必要はありません。

以下のフラグが有効になります。(よく使うものだけ)

フラグ名 フラグの意味
IMAGE_SCN_CNT_CODE 実行用コード領域になります。
IMAGE_SCN_CNT_INITIALIZED_DATA 初期化済みデータ領域になります。
IMAGE_SCN_CNT_UNINITIALIZED_DATA 未初期化データ領域になります。
IMAGE_SCN_MEM_NOT_CACHED このセクションをメモリ上にキャッシュしません。VirtualAlloc系のPAGE_NOCACHEに相当します。
IMAGE_SCN_MEM_NOT_PAGED このセクションをページファイル上におかないようにします。
IMAGE_SCN_MEM_EXECUTE このセクションを実行可能として確保します。VirtualAlloc系のPAGE_EXECUTE系属性を持ちます。
IMAGE_SCN_MEM_READ このセクションを読み込み可能として確保します。VirtualAlloc系のPAGE_READ系属性を持ちます。
IMAGE_SCN_MEM_WRITE このセクションを書き込み可能として確保します。VirtualAlloc系のPAGE_WRITE系属性を持ちます。

一番重要なのはCharacteristicsメンバだったりします。この領域はなぜか各プログラムのランタイムから参照されることがあるようで、

ちゃんと実行可能な領域は仮想メモリの状態だけではなくこのメンバのフラグも正しく更新しないとプログラムエラーとなっちゃうことが多々あります。

また、仮想メモリを自前で確保するときはできるだけこのフラグのメモリ状態を設定するようにします。でないとDEPやらいろいろなチェックに引っかかります。

また、IMAGE_SCN_CNT_CODEをつけなくてもIMAGE_SCN_MEM_EXECUTEのフラグがあるならそのセクションは実行可能です。なのでその部分に実行用コードをおくこともできます。

自己圧縮形ではこのような形にすることもありますが、あまりそのような状態にするのはおすすめしませんが・・・。

ちなみに、VisualC++系でプログラムを作るとセクションはデフォルトではこんな感じになります。

セクション名 メモリ保護状態 セクション領域の使用状態
.text CODE + EXECUTE + READ 実行用コード領域
.rdata INIT + READ プログラム内の固定文字列や定数などの読み込みのみのメモリ領域
.data INIT + READ + WRITE static変数やグローバル変数、クラス情報などの共有変数領域
.rsrc INIT + READ リソースデータ領域

DLLのインポート情報なども.rdataにあることが多いです。(初期化したときはローダにより一時的にWRITE属性が与えられるため)

ということで駆け足でWindowsの実行ファイルのヘッダデータをみてきました。

ちょっと複雑な構造をしていますが、すべての実行ファイル(exeやdllだけではなくsysなどのドライバも)にあるものなので覚えておくとちょっと使えます。

次回からそれぞれのセクションのデータについてふれてみましょう。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

この記事のトラックバック用URL