今回は,Windows API(Application Programming Interface)とセキュリティの関係を,次のような手順で検討することにします。
手順1:単純なサンプル・プログラムの作成
手順2:ツールを使ったWindows API呼び出しのセキュリティ・ホールの検出
手順3:Windows API呼び出しの問題点
なお,「Application Verifier」というセキュリティ・ホール検出ツールを使用しますが,今回は紙面の都合でツールの使い方は説明いたしません。Application Verifierの機能とその使い方は,今後の連載で詳しく説明する予定です。このApplication Verifierツールは、前回紹介したDependency Walkerと同じように、一般Windowsユーザでも比較的簡単に活用できます。今回は,「そういうツールもあるのか」程度の軽い気持ちで以降の解説を読んでください。それでは,本論に入りましょう。
単純なサンプル・プログラムの作成
今回のサンプル・プログラムのソースコードをまずご覧ください。
#include "windows.h" #include "ItproToyotaNo10.h" using namespace ItproToyotaNo10; int main() { CheckAPI(); return 0; }
ご覧のように,メイン実装部の構造はきわめて単純なものです。windows.hヘッダー・ファイルをインクルードしていることから分かるように,このサンプル・プログラムはWindows固有機能を使用します。また,using namespace文を記述していることから分かるように,Windows固有機能を呼び出すコード実装部は,名前空間ItproToyotaNo10内に隠ぺいされています。main関数内ではCheckAPIという関数を呼び出していますが,このCheckAPI関数は,ItproToyotaNo10.hヘッダー・ファイル内で次のように宣言しています。
namespace ItproToyotaNo10 { void CheckAPI(); }
ご覧のように,CheckAPI関数は,名前空間ItproToyotaNo10に所属しています。名前空間には,CheckAPIのような関数だけではなく,クラスや構造体などのユーザー定義型も所属させることができます。このような名前空間の役割は,C#やJavaプログラムでも基本的に同じですから,名前空間の役割をここでしっかり覚えておきましょう。
今回焦点となるWindows APIは,CheckAPI関数の実装部に(隠ぺいされるように)記述されていますから,実装部を検討してみましょう。次のソースコードをご覧ください。
BOOL bRet = CreateProcess ( NULL, (LPTSTR)"notepad.exe c:\\test.txt", NULL, NULL, FALSE, 0, NULL, NULL, &si , &pi ) ;
このソースコードは,Windows API関数としてCreateProcessを使用していることを示しています。CreateProcess関数は,その名称から分かるように,実行可能プログラムをメモリーにロードし,それをWindowsプロセスとして起動する機能を持っています。プロセスは,Windows OSのきわめて重要なオブジェクトの1つです。今回はプロセスの詳細にはこれ以上踏み込みませんが,プロセスはWindowsの管理下で活動するきわめて重要なオブジェクトである,と考えておいてください。
このCreateProcess関数が実行されると,メモ帳が起動され,c:\test.txtファイルの内容が表示されます。今回のプロジェクト・ファイルはこちらからダウンロードできますから,サンプル・コードを実行するときには,c:\test.txtを事前に作成しておいてください。test.txtファイルの内容は何でもかまいません。
ところで皆さん,このCreateProcess関数ですが,実は,セキュリティ・ホールを発生させてしまう原因を抱えています。その原因がどこにあるのか分かますか?
私たちは限られた時間内で作業を完了しなければなりませんから,どのような経験者でもこのようなAPI呼び出しコードを記述してしまう可能性があります。つまり,個々のAPIの詳しい使い方などをいちいち記憶していませんし,また,できません。このため,セキュリティ・ホール発生の原因を自動検出し,それを警告してくれるようなツールがあれば,と誰もが望みます。実は,そのようなツールがすでに一般公開されています。それではそのツールを実際に使ってみましょう。
ツールを使ったWindows API呼び出しのセキュリティ・ホールの検出
図1●サンプル・プログラムの動作をApplication Verifierを使って検証する |
- 2つのプロセス(ITProNo10.exeとnotepdad.exe)が起動された。
- しかし,起動された2つのプロセスはいずれも深刻な問題を抱えている。
画面から分かるように,メモ帳も内部に問題を抱えていますが,今回は,ITProNo10.exeの実行時に検出された警告にのみ焦点を当てることにします。警告内容の詳細説明は次のようなものです。
The lpApplicationName argument is NULL,lpCommandLine has spaces,and the exe name is not in quotes.
この警告文は次のようなことを述べています。
- 引数lpApplicationNameはNULLである。
- 引数lpCommandLine内には空白が含まれている。
- 実行可能プログラム名は引用符で囲まれていない。
Application Verifierはこれらの問題点を検出し,最終的に,次のような警告を行っています。
「これらの問題が解決されない限り,セキュリティ問題が発生する可能性がある
このような警告メッセージを目にしたソフトウエア開発者の多くは,「このままじゃ納品できない!」と判断すると思います。警告を出さなくする対策が必要になります。さて皆さんなら,どのような対策を採りますか?
まずは先ほどの警告内容をじっくり読んでみましょう。いずれの警告もCreateProcess関数に渡している引数に原因があることを指摘していることが分かると思います。このような場合,CreateProcess関数に関するMicrosoft公開説明文に目を通す必要があります。ベテランWindows開発者であっても,時には勘違いをすることもあります。さらにいえば,公開説明内容は時の流れの中で更新されることが常識となっています。
Windows API呼び出しの問題点
Microsoftは,こちらでCreateProcess関数に関する詳細な情報を公開しています。公開されている情報を精読してみると,次のような文章を目にすることができます。
Security Remarks
The first parameter,lpApplicationName,can be NULL,in which case the executable name must be in the white space-delimited string pointed to by lpCommandLine. If the executable or path name has a space in it,there is a risk that a different executable could be run (以下省略)
この文章は,「Security Remarks(セキュリティに関する注意書き)」というタイトルを持ち,次のような意味のことを述べています。
「第1パラメータlpApplicationNameはNULLとすることができます。NULLとした場合,第2パラメータlpCommandLineに,空白を含む実行可能ファイル名文字列へのポインタを指定することができます。しかし,第2パラメータに空白を含めた場合は大変危険です。」
意味の取り方がかなり強引ですが,「空白を含めることができるが,それは危険である」という説明は多くの開発者にとっては大変分かりにくいものといってよいでしょう。さらにいえば,「第1パラメータlpApplicationNameはNULLとなることができます。」という説明は,次のような理由から常識を欠いているものです。
「CreateProcess関数は,第1パラメータlpApplicationNameに指定されたアプリケーションをプロセスとして起動する機能を提供している。このため,常識から判断すれば,起動されるアプリケーション名が省略されることはありえないはずである。第2パラメータlpCommandLineは,第1パラメータlpApplicationNameに指定されたアプリケーションに渡す情報を指定するためのものである。」
この解釈は,私たちの常識に基づいており,大変分かりやすいと思います。そこで私は,次のようなコードを考えてみました。
BOOL bRet = CreateProcess ( (LPCTSTR)"c:\\windows\\system32\\notepad.exe", (LPTSTR)" c:\\test.txt", NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi ) ;
図2●コードを実行すると,メッセージが出る |
ご覧のように,警告は消え,改善通知メッセージのみを表示してきます。改善通知メッセージの内容は,次のようになっています。
The application used a Windows System Directory path that was not obtained using a method approved by the Designed for Windows Logo Program. Use the correct API function when accessing the Windows System directory path: GetSystemDirectory()
この改善通知メッセージは,次の2点への注意を喚起しているにすぎません。
- 「c:\\windows\\system32\\notepad.exe」という固定情報が記述されている。
- システムディレクトリ情報はWindows環境に応じて異なるため,GetSystemDirectory()経由で取得するとよい。
この指導は,「GetSystemDirectory()というWindows API関数を呼び出し,Windows情報取得操作を抽象化せよ!」ということですから,初級プログラマでも簡単に従うことができると思います。
ちなみに「Security Remarks」の最後の方には、次のような説明文(結論)が載せられています。
To avoid this problem, do not pass NULL for lpApplicationName. If you dopass NULL for lpApplicationName, use quotation marks around the executablepath in lpCommandLine(一部省略)
第1説明文は、「第1パラメータlpApplicationNameにNULLを渡してはならない」と述べていますが、その理由は一切紹介されていません。そして第2説明文は、次のようなコードを記述するように推奨しています。
BOOL bRet = CreateProcess ( NULL, (LPTSTR)"\"c:\\windows\\system32\\notepad.exe\" c:\\test.txt", NULL, NULL, FALSE, 0, NULL, NULL, &si , &pi ) ;
すでに触れたように,この推奨コードは私たちの常識からかい離しています。CreateProcess関数には過去のいろいろないきさつがあると思いますが,現在公開されている説明は「分かりにくい」といえます。「説明が分かりにくければ」,開発者の間にいろいろな解釈を生んでしまいます。
「プロセスとして起動するアプリケーションを指定する第1パラメータがNULLとなることができる」という解釈は,常識から判断する限り,「分かりにくい」ものです。「起動していないアプリケーションにパラメータだけを渡す」という行為は,現代の常識を超えています。ちなみに、このCreateProcessに関する「SecurityRemarks」は、Trojanの教訓を基に起草されたといわれています。
今回のまとめ
- 分かりにくいWindows API説明文が公開されている
- 分かりにくいWindows API説明は開発者の間に混乱を発生させる
- 開発者間の混乱は,不測の事態が発生する原因となる
今回は以上で終了です。次回またお会いいたしましょう。ごきげんよう!