今回はWindowsユーザーとプログラミング言語の関係を取り上げます。Windowsユーザーは,基本的には,ビルドされた実行ファイルを利用するエンド・ユーザーを指しますから,プログラミング言語に関する詳細な知識を持っている必要はありませんし,その義務もありません。

 しかし,利用する実行ファイル内で問題が発生すれば,実害を直接受けてしまいます。その問題の原因がバッファ・オーバーラン(BOR)などにある場合,すでに触れたように,ユーザーであるにもかかわらず,インターネット経由で問題をまき散らす加害者になってしまいます。そこで今回は,IT時代を生き抜くための,次のような基礎知識を学習することにします。

  • 使用されているリンカー・バージョンを調べる方法
  • プログラミング言語CとC++コードの現実的な相違点を調べる方法

 これら2つの項目は一見すると難しそうですが,習得しておいて決して損はしない知識です。また,それほど難しいものでもありません。それではさっそく,作業に取り掛かりましょう。

リンカー・バージョンを調べる方法

 この連載ですでに触れたように,米MicrosoftのBill Gates氏はセキュリティ・サポート・オプションを備える最新開発ツール環境でビルドする重要性を強調しています。私たちWindowsユーザーとしては,shdocvw.dllファイルのビルド時に使用されたリンカー・バージョンを知りたいところです。

 私たちが記述したソースコードはコンパイラとリンカーの処理を経て,最終的に実行プログラムに変換されます。コンパイラは主にソースコード内の整数や文字列などの「型の整合性」をチェックし,オブジェクト・ファイルを作成します。リンカーはコンパイラの成果物である(複数の)オブジェクト・ファイル内の,「名前の整合性」をチェックし,目的の実行ファイルを作成してくれます。これら2種類の整合性チェックではいろいろな問題が発生しますが,コンパイル時に検出されるエラーはコンパイル・エラー,リンク時に検出されるエラーはリンク・エラー,とそれぞれ呼ばれています。

 最新バージョンのリンカーが使われているのであれば,セキュリティ・サポート・コードが自動的に組み込まれ,Microsoft社内で出荷前の厳しいテストが行われているのでは,と期待できます。

 筆者はこの連載で修正パッチ情報を収集するサンプル・プログラムを紹介しました。そのサンプルをWindows Server 2003環境で実行すると,2004年1月20日現在,次のような情報が返されました。

File: 1
BuildDate: Sat Oct 25 02:38:27 2003
FileName: shdocvw.dll
Location: C:\WINDOWS\system32
Version: 6.0.3790.94

File: 2
BuildDate: Sat Oct 25 02:38:27 2003
FileName: mshtml.dll
Location: C:\WINDOWS\system32
Version: 6.0.3790.94

 このデータは表示される情報のほんの1部にすぎません。なお,このサンプル・プログラムは,Windows 2000をはじめとする各種のWindows環境で実行できますから,まだ試していない方はぜひダウンロードし,実行してみるとよいでしょう。ここでは,「File: 1」情報を参考例として使用します。

 「File: 1」情報は,現在インストールされているshdocvw.dllのビルド日付とバージョン番号などを提供してくれています。残念ながら,ビルド時に使用されたリンカー・バージョンなどに関する情報は一切含まれていません。

図1●Dependency Walkerで取得したリンカー・バージョンの情報
 一部の方は,shdocvw.dllファイルのリンカー・バージョンを知るには専門的な知識が必要になるのではないか,と不安を感じておられるかもしれません。結論を先に言うと,それは取り越し苦労にすぎません。リンカー・バージョンを簡単に取り出せる優れた無料ツールがすでに一般公開されています。図1[拡大表示]の情報をご覧ください。

 この情報は,Dependency Walkerという無料ツールで取得しています。入手したい方は,こちらからダウンロードしてください。このツールは,MicrosoftのPlatform SDKにも含まれています。それでは,上記画面の「注目1」を見てみましょう。

 「注目1」には,「Linker Ver」という文字列があり,値として「7.10」が格納されています。shdocvw.dllはリンカー・バージョン7.10で作成されていることが分かります。このバージョン番号は,Visual Studio .NET 2003に含まれているリンカー・バージョンと同じですから,2003年1月20日現在公開されている最新リンカーといってもよいでしょう。Microsoftの担当開発者がビルド時にセキュリティ・サポート・オプションを効果的に活用してくれていれば,shdocvw.dllのセキュリティ問題はかなり改善されている,と期待してよいことになります。

 さて皆さん,この無料で入手できるDependency Walkerツールですが,リンカー情報以外に多数の貴重な情報を提供してくれます。例えば,上記画面内の「注目2」を見ると分かるように,shdocvw.dllはプログラミング言語Cで記述されています。ご承知のように,Cを使用するのは,システム・レベルのかなり高度な技術力が必要,といわれています。

 Java言語を設計し,最初に実装したといわれているSun MicrosystemsのJames Gosling氏は,「JavaはCにはない配列境界チェックなどを追加し,Cの弱点を修正しているため,バッファ・オーバーランが発生する確率は低い」と述べています。また,C++の生みの親であるBjarne Stroustrup氏は,「Cの使い方を誤れば,自分の足を簡単に撃ち抜いてしまう」と述べています(詳細はこちらを参照)。

 2人の意見を総合すると,プログラミング言語Cによるプログラム作成は大変な危険を伴うことが分かります(もちろん,担当開発者の力量に依存しますが)。基本的には,システム・プログラミング言語であるCではなく,より抽象化の進んだ言語体系を持つJavaやC++のほうがセキュリティ・リスクが少なくなると考えてよいと思います。そこで次に,Dependency Walker無料ツールを活用して,CとC++コードの相違点を見てみることにします。

CとC++コードの相違点を調べる方法

 前回はプログラミング言語C++を採用し,標準ライブラリに宣言・実装されているstringクラスを使用しました。stringクラスは自分自身を知っていますから,セキュリティ問題もかなり改善されます。ところがご承知のように,クラスという考え方は,プログラミング言語Simulaからプログラミング言語C++に取り入れられたものですから,C++の基になったC言語では利用できません。ここでは,セキュリティという視点から,CコードとC++の相違点を調べることにします。決して難しいことはありませんから,ぜひ最後まで目を通してみてください。

 まずは,次のような準備作業をしましょう。

準備1:Dependency Walkerをダウンロードし,インストールする。この操作は,zipファイルをダウンロードし,解凍するだけです。

準備2:第5回で紹介したCサンプル・プロジェクトをダウンロードする

準備3:第8回で紹介したC++サンプル・プロジェクトをダウンロードする

図2●第5回のサンプル・コードは,プログラミング言語Cで記述されていることが分かる
 以上で準備は完了です。第5回のサンプル・プロジェクトは,プログラミング言語Cを採用しています。また,第8回連載では標準C++コードのみでサンプル・プログラムを作成しています。この2点をきちんと頭の中に入れて置いてください。

 それでは今度は,次のような操作をしましょう。

操作1:Dependency Walkerを起動し,「File/Open」から第5回のサンプル・プログラム(itprono5.exe)を選択します。

操作2:Dependency Walkerの「Profile/Start Profiling...」をクリックします。クリックするとダイアログボックスが開きますが,何も考えずに「OK」ボタンを押してください。図2[拡大表示]のような画面が表示されるはずです。

 図2の画面が表示されたら,画面内に挿入した私のコメントに目を通してください。第5回のサンプル・コードは,間違いなくプログラミング言語Cで記述されていることが分かります。またこのアプリケーションは,形としてはWindowsカーネルを直接制御しています。これは、生のAPI(Application Programming Interface)を直接操作するにとになりますから,目的のAPIの性質や使い方をきちんと理解する必要があることを意味します。具体的に言えば,第5回サンプル・コード内のCopyMemory関数の転送バイト数の管理などに注意する必要があります。それでは今度は,次の情報をご覧ください。

Started "ITPRONO5.EXE" (process 0xDD8) at address 0x00400000. Successfully hooked module.
Loaded "NTDLL.DLL" at address 0x77F20000. Successfully hooked module.
Loaded "KERNEL32.DLL" at address 0x77DE0000. Successfully hooked module.
Injected "DEPENDS.DLL" at address 0x08370000.
Entrypoint reached. All implicit modules have been loaded.
GetProcAddress(0x77DE0000 [KERNEL32.DLL],"InitializeCriticalSectionAndSpinCount") called from "ITPRONO5.EXE" at address 0x0040A8A7 and returned 0x77DE2D95.
GetProcAddress(0x77DE0000 [KERNEL32.DLL],"FlsAlloc") called from "ITPRONO5.EXE" at address 0x00404BB2 and returned 0x77DEEA58.
GetProcAddress(0x77DE0000 [KERNEL32.DLL],"FlsGetValue") called from "ITPRONO5.EXE" at address 0x00404BC6 and returned 0x77DE1DEB.
GetProcAddress(0x77DE0000 [KERNEL32.DLL],"FlsSetValue") called from "ITPRONO5.EXE" at address 0x00404BDA and returned 0x77DEA217.
GetProcAddress(0x77DE0000 [KERNEL32.DLL],"FlsFree") called from "ITPRONO5.EXE" at address 0x00404BEE and returned 0x77E01FB5.
Exited "ITPRONO5.EXE" (process 0xDD8) with code 0 (0x0).

 この情報は難しい印象を与えますが,アプリケーションとWindowsカーネルの関係をはっきり示しています。上から取り出した例えば,次の一部分の意味を考えてみましょう。

GetProcAddress(0x77DE0000 [KERNEL32.DLL],"InitializeCriticalSectionAndSpinCount") called from "ITPRONO5.EXE" at address 0x0040A8A7 and returned 0x77DE2D95.

図3●アプリケーションとWindowsカーネルの結び付きが弱くなっている
 この情報は次のようなことを私たちに教えてくれています。

アプリケーションITPRONO5.EXEは,メモリロケーション0x0040A8A7からGetProcAddress関数経由でKERNEL32.DLLを直接呼出し,InitializeCriticalSectionAndSpinCount関数のアドレスを得ている

 ここでは,アプリケーションとWindowsカーネルの結び付きが強い,ことを記憶しておいてください。

 それでは,同じような操作手順で第8回のサンプル・コードも調べてみましょう。先ほどの操作を繰り返すと,最終的に図3[拡大表示]のような画面が表示されてくるはずです。

 先ほどと同じように,画面内に挿入した私のコメントに注目してください。第8回サンプル・コードはプログラミング言語Cではなく,C++で記述されていることがはっきり分かります。また,アプリケーションとWindowsカーネルの結び付きが弱くなっていることも分かります。

Started "NO8CPP.EXE" (process 0xA54) at address 0x00400000. Successfully hooked module.
Loaded "NTDLL.DLL" at address 0x77F20000. Successfully hooked module.
Loaded "KERNEL32.DLL" at address 0x77DE0000. Successfully hooked module.
Injected "DEPENDS.DLL" at address 0x08370000.
Loaded "MSVCP71D.DLL" at address 0x10480000. Successfully hooked module.
Loaded "MSVCR71D.DLL" at address 0x10200000. Successfully hooked module.
Entrypoint reached. All implicit modules have been loaded.
GetProcAddress(0x77DE0000 [KERNEL32.DLL],"InitializeCriticalSectionAndSpinCount") called from "MSVCR71D.DLL" at address 0x1020D297 and returned 0x77DE2D95.
GetProcAddress(0x77DE0000 [KERNEL32.DLL],"FlsAlloc") called from "MSVCR71D.DLL" at address 0x10203362 and returned 0x77DEEA58.
GetProcAddress(0x77DE0000 [KERNEL32.DLL],"FlsGetValue") called from "MSVCR71D.DLL" at address 0x10203376 and returned 0x77DE1DEB.
GetProcAddress(0x77DE0000 [KERNEL32.DLL],"FlsSetValue") called from "MSVCR71D.DLL" at address 0x1020338A and returned 0x77DEA217.
GetProcAddress(0x77DE0000 [KERNEL32.DLL],"FlsFree") called from "MSVCR71D.DLL" at address 0x1020339E and returned 0x77E01FB5.
GetProcAddress(0x77DE0000 [KERNEL32.DLL],"IsProcessorFeaturePresent") called from "MSVCR71D.DLL" at address 0x1025A159 and returned 0x77DEE80D.
Exited "NO8CPP.EXE" (process 0xA54) with code 0 (0x0).

 例えば,次の一部分に注目してみましょう。

GetProcAddress(0x77DE0000 [KERNEL32.DLL],"InitializeCriticalSectionAndSpinCount") called from "MSVCR71D.DLL" at address 0x1020D297 and returned 0x77DE2D95.

 この情報は,次のようなことを私たちに教えてくれています。

アプリケーションNO8CPP.EXEは,C++層(MSVCR71D.DLL)経由でKERNEL32.DLLを間接的に呼出し,InitializeCriticalSectionAndSpinCount関数を間接的に使用している

 この文章では,「間接的」という表現が2個所で使われています。この場合の「間接的」とは,Windowsカーネル管理,より具体的に言えば,メモリーや同期オブジェクト(メモリーなどのシステム資源をスレッド間で共有できるようにするための仕掛け)などのシステム資源管理はその道の専門家に任せてしまう,ということです。簡単に言えば,技術詳細(具体的には,前回紹介したstringクラスのsize/lengthメソッドなどの実装コード)の隠ぺいであり,開発作業の分業・効率化です。システム資源とその管理手法はプラットフォーム固有のものですから,その管理方法を一番知っているのはプラットフォームの開発者です。仮想マシンとその環境用のプログラミング言語の組み合わせ出荷形態は,この考え方をさらに一歩推し進めたものと考えてよいでしょう。

 なお,今回紹介したDependency Walker出力情報は,Windows Server 2003環境で取り出しています。皆さんのお使いになっているWindows環境で得られる情報と異なると思います。しかし,出力情報の活用方法は同じです。ちなみに,Dependency Walkerは,Windows 95からWindows XPまで対応しています。

 エンド・ユーザーがここまでの知識を持つ必要があるのか? という疑問を持っている人もいるかもしれません。それは自然な疑問ですが,Windowsを使用する経験が長くなれば,後輩社員からいつどのような質問を受けることになるか分かりません。恥をかくのは誰しも避けたいものです。また,人事異動の結果,このような知識がいつ必要になるか予測できません。備えあれば憂いなし,です。今回の連載記事は,IT業界に所属するすべての人の「身だしなみ」とでも考えておきましょう。

 一方,開発者の方は,出力情報内のFlsAllocやFlsGetValueなどのカーネル関数に興味の目を向けているかもしれません。詳細を調べたい場合には,こちらをご覧ください。ちなみに,「FlsAlloc」の「Fls」は,「fiber local storage」の略語です。

今回のまとめ

  • リンカー・バージョン番号が大きい場合,そのコードのセキュリティ・リスクは低くなっている可能性がある
  • C++プログラミング言語を採用することは,アプリケーション・コードとWindowsシステム・コード間に抽象化層を追加することである

 今回は以上で終了です。次回またお会いいたしましょう。ごきげんよう!