解説インサイド .NET Framework [改訂版]第5回 アセンブリのロードとセキュリティ 吉松 史彰2003/07/30 改訂(改訂前の記事はこちら) |
本稿は、2002/08/22に公開された記事を、.NET Frameworkの新しいバージョンである「.NET Framework 1.1」に対応させ、全面的に加筆・修正を行った改訂版です。 |
はじめに
Back Issue
|
||||||||
|
前回まで、厳密名付きアセンブリがロードされる手順を解説してきた。しかし、そもそもその手順は何らかのコードから型を参照し、その結果共通言語ランタイム(CLR)がアセンブリをロードする決断をして初めて行われるものだ。では、そもそもVisual Studio .NETなどで開発されたいわゆるEXEアプリケーションは、一番初めはどうやってメモリ上にロードされ、実行が始まるのだろうか。今回は、無の状態からCLRの環境を作り出すまでのWindows OSのローダーの仕組みと、そこから起動されるCLRの動作を解説し、その過程で行われるCLRのセキュリティ機能を概観する。
Windowsローダーの動作
.NET FrameworkはWindows上で動作するアプリケーション実行環境だ。だが、Windows 2000とそれ以前のOSは、まだCLRの形がなかった時代に出荷されたOSだ。そのため、CLR上で動作するアプリケーション(アセンブリ)はWindowsの作法に従って起動されなければならない。CLRに対応するプログラミング言語のコンパイラは、その作法に従ったコードをモジュールに埋め込むようになっている(CLRの世界では、物理ファイルはモジュールであることを思い出してほしい)。
Windowsの作法に従ったコードは、Portable Executable(PE)形式で出力されたファイルに入っている。この形式は、Windowsのローダーが読み込んでメモリに正しく配置する方法を知っている形式だ。このファイルの構造は、Microsoftが提供しているWindowsアプリケーションの開発キットであるPlatform SDKで配布されているWinnt.hにIMAGE_**_HEADERという構造体として定義されている。例えば、WindowsにロードできるPE形式のファイルの先頭には必ず“IMAGE_DOS_SIGNATURE”として定義されている定数が入っている(これは文字列にするとMZという2文字になる。MS-DOS 2.0のプログラム・マネージャであったMark Zbikowski氏の頭文字であるらしい)。何でもいいので手元にあるEXEファイルやDLLファイルをメモ帳で開いてみてほしい。先頭にMZが確認できるはずだ。
PE形式のファイルには、決められたヘッダ構造がファイルの先頭に含まれている。32bitのWindows向けのPEファイルの場合、Visual C++またはVisual Studio .NETに付属しているdumpbin.exeというツールでヘッダ情報を見ることができる。例えば、「第3回 アセンブリのロード」で作成したuser.exeに対して、次のようなコマンドでPEファイルのヘッダを見ることができる。
% dumpbin.exe /headers user.exe
dumpbin.exe /headersの実行結果画面 |
このヘッダでいろいろなことが分かるのだが、今回の議論でまず触れたいのは、「OPTIONAL HEADER VALUES」というセクションに入っている次の値だ。
230E entry point (0040230E)
dumpbin.exe /headers実行結果の「OPTIONAL HEADER VALUES」部分 |
ここに書いてあるのは、(乱暴にいうと)Windowsのローダーがこのファイルをメモリ上に配置した後、最初に実行されるコードが入っているアドレスだ。Windowsのローダーはファイルをロードするとこのアドレスにジャンプして、そこから命令の実行を開始する。
PE形式の大きな特徴は、ファイル上の配置と実行時のメモリ上の配置が(セクションごとに)同じになることだ。つまり、ファイルの中を見れば、メモリの中を見たのと同じことになる。それでは、user.exeで最初に実行されるコードはどんなものになっているのだろうか。それは、上記のアドレス(0x0040230E)に配置されるものを見れば分かる。先ほどのdumpbin.exe /headersの結果をたどっていくと、「SECTION HEADER #1」というセクション(セクションの名前は.text)には次のようなエントリがある。
314 virtual size
2000 virtual address (00402000 to 00402313)
400 size of raw data
200 file pointer to raw data (00000200 to 000005FF)
dumpbin.exe /headers実行結果の「SECTION HEADER #1」部分 |
この内容は、メモリ上で0x00402000から0x00402313の間にロードされるコードは、このファイルの中の0x00000200バイト目から0x000005FFバイト目の間に入っているということを示している。先ほどの「entry point」に書いてあったアドレスは0x0040230Eなので、ファイル中の0x00000200バイト目から0x30Eバイトを足した場所、つまり0x0000050Eバイト目から書いてある機械語コードが、Windowsローダーが最初に実行するコードになる。user.exeの場合、ここは次のようなバイト列になっている。
FF 25 00 20 40 00
これは、x86機械語命令で、アセンブリ言語に直すと次のようなコードを意味する。
jmp 0x00402000
つまり、メモリ上の0x00402000番地に飛んで、そこからコードの実行を継続するというコードになっている。このコードが、かつて「.NETウイルス」が話題になったときによく紹介された「スタブ・コード」と呼ばれるものだ。この番地にロードされているコードは、次のようなコマンドで確認できる。
% dumpbin.exe /imports user.exe
dumpbin.exe /importsの実行結果画面 |
つまり、0x00402000番地に入っているのはmscoree.dllに入っている_CorExeMainという関数である。mscoree.dllは.NET Frameworkのインストール時に%Systemroot%\System32に配置されるDLLで、user.exeの起動とともにメモリ上にロードされる。ロードされるアドレスは実行時に上書きされるため、実際には0x00402000にロードされるわけではない。実行時にどこにロードされるかは、bind.exeというプログラムで確認できる(このプログラムは通常、「\Program Files\Microsoft Visual Studio .NET\Common7\Tools\Bin」にインストールされる)。
% bind.exe -v user.exe
bind.exe -vの実行結果画面 |
この場合、_CorExeMain関数は0x7917B865地点にロードされるということを示している。
ここまでの動作をまとめると、次のようになる。
- (ダブル・クリックするなど、何らかの手順で)user.exeが起動される。Windowsはuser.exeをメモリ上に配置する。
- Windowsはuser.exeのヘッダを確認して、“entry point”のアドレスを確認する。
- Windowsはentry pointのコードを実行する。すると、すぐにmscoree.dllの_CorExeMain関数に飛ぶ。
- _CorExeMain関数の実行が始まる。
なお、ここまでの解説はWindows XP以前のWindowsローダーが行う手順だ。Windows XPとWindows Server Familyの各OSは、CLRについてよく知っている。そのため、これらの新しいOSはスタブ・コードを必要としない。プログラムが開始されると、ローダーはまずPEファイルの中にCLRベースのプログラムであることを示す証拠を探し、それが見つかるとentry pointを無視してすぐにmscoree.dllをロードし、その_CorExeMain関数に処理を引き渡すようになっている。そのため、Windows XP上Windows Server 2003では上記のコードの動作は確認できない。また、これらのOSではスタブ・コードに感染するタイプの「ウイルス」は、感染はしても発症はしないこともここから分かる。
ユーザー(あるいはほかのプログラム)から呼び出されたものがEXEではなくDLLだった場合、スタブ・コード(あるいはWindows XPローダー)が呼び出す関数は_CorDllMainという関数になる。この関数もやはりmscoree.dllに含まれていて、下記の手順をそっくりそのまま使ってCLRを呼び出す。
INDEX | ||
解説 インサイド .NET Framework [改訂版] | ||
第5回 アセンブリのロードとセキュリティ | ||
1.Windowsローダーの動作 | ||
2.CLRのロード | ||
3.署名の検証とプログラムの開始 | ||
4.いくつものセキュリティ・チェック | ||
「解説:インサイド .NET Framework [改訂版]」 |
- 第2回 簡潔なコーディングのために (2017/7/26)
ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている - 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう - 第1回 明瞭なコーディングのために (2017/7/19)
C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える - Presentation Translator (2017/7/18)
Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
|
|