はじめに
この記事では、Windows XP SP2(以下、「XPSP2」と略記)より実装されたDEP機能について考察を行います。
今回、デモプログラム(checknx)を作成しました。このプログラムよりどのようにDEP機能が働いているかを確認し、実際にメモリが保護されているかを考察します。
対象読者
- CPUの働き、特に NX-bitがどのように働くのか興味がある方
- Visual C++ によるインラインアセンブラの実装に興味がある方
必要な環境
Visual C++ version 6 SP6(MFC)で開発を行いましたが、他の開発環境でも簡単に移植できると思っています。できれば「NX-bitをサポートしたシステム」+「XPSP2」上でテストしてください。
- NX-bit/XD-bitが有効なシステム
- XPSP2/Windows Server 2003 SP1
Windows 2000以降で動作しますが、Windows 2000にはDEPは実装されていません。
データ実行防止(DEP)機能について
ハードウェアDEPとソフトウェアDEP機能
DEP(Data Execution Protections:データ実行防止)機能とは、XPSP2/Windows Server 2003 SP1 より新たにサポートされたセキュリティ機能で、悪質なプログラムによるバッファ・オーバーランなどの攻撃からシステムを守る働きをします。
DEP機能には、CPUに備わったメモリ保護機能(NX-bit/XD-bit)を利用してバッファ・オーバーランを検知する「ハードウェアDEP機能」と、ソフトウェアのみによるバッファ・オーバーランを検知する「ソフトウェアDEP機能」の2通りがあります。「ソフトウェアDEP機能」は全てのシステムで適用されますが、「ハードウェアDEP機能」の場合、CPU自身が NX-bit/XD-bitという機能を持ち合わせていないと動作しません。
今回は、この NX-bit/XD-bitを使用した「ハードウェアDEP機能」に焦点を合わせます。
NX-bit/XD-bit
NX-bit(No eXecute)は、AMDがCPUに搭載したメモリ保護機能です。
AMDではこの機能のことを「拡張ウィルス防止機能(Enhanced Virus Protection)」と命名しています。後にIntelも同様の機能を搭載していますが、名称は NX-bitではなく「XD-bit(eXecution Disable bit:エグゼキュート・ディスエーブル・ビット)」と命名しました。しかし、機能的には NX-bit、XD-bitともに同様と考えてよいでしょう。最近では、VIAや TransmetaのCPUにも NX-bitが採用され始めています。
以後、NX-bit/XD-bitのことを NX-bitと記載します。
NX-bitのメモリ保護機能
NX-bitは、メモリの実行可能・不可能の種類を示すメモリ管理領域のフラグです。
NX-bitが有効な場合、実行不可能なメモリ上でプログラムを実行しようとするとCPUは保護例外を起こし、システムに「ホントに実行していいの?」とお伺いを立てます。システムは保護例外に対し、意図して実行を続けることもできますし、その場で例外エラーを発生させることも可能です。
単純な機能ですが、セキュリティ面でかなり役立つ機能と言われています。
悪質なプログラムの多くは、バッファ・オーバーランというプログラム上のバグを利用してシステムを破壊します。 バッファ・オーバーランは、プログラムを開発する上で非常に発見しづらく、セキュリティが重要視される今日ですらバッファ・オーバーランによる攻撃がいまだに行われています。NX-bitは、このバッファ・オーバーラン攻撃をハードウェア的に検知し、未然にメモリを保護することを目的として開発されています。
ハードウェア/ソフトウェアDEP機能の確認方法
以下の手順に従って[データ実行防止]タブを開き、適用されているDEP機能を確認します。
- システムのプロパティを開く
[スタート]→[コントロールパネル]を選択し、コントロールパネルで[パフォーマンスとメンテナンス]→[システム]を選択します。
- データ実行防止を開く
[システムのプロパティ]ダイアログの[詳細設定]タブを選択し、[パフォーマンス]の[設定]ボタンをクリックします。[パフォーマンスオプション]ダイアログの[データ実行防止]タブを選択します。
- ハードウェア・ソフトウェアDEPの確認
- ソフトウェアDEPのみ有効な場合(A.)
ダイアログの下に「お使いのコンピュータのプロセッサでは、ハードウェアによるDEPはサポートされません。ただし、DEPソフトウェアを使用することにより、ある種類の攻撃を阻止することができます。」とのメッセージが表示されます。
- ハードウェアDEPが有効な場合(B.)
上記のメッセージは表示されません。見分け方はこの程度で非常に分かりづらいです。
DEPには以下の2通りがあります。デフォルトでは「システム内のモジュールのみ有効(a.)」に設定されています。
- システム内のモジュールのみに有効(a.)
- Windows内のプログラム全部に有効(b.)
上記よりDEPには以下の4通りの設定があります。この中では、設定「4」が一番厳しいDEPとなります。
- (A.)-(a.) (設定「1」)
- (A.)-(b.) (設定「2」)
- (B.)-(a.) (設定「3」)
- (B.)-(b.) (設定「4」)
checknxによるデモ
ハードウェアDEP機能の確認を確認するためサンプルプログラム「checknx」を作成しました。以下に、XPSP2上で動作確認を行った結果を示します。
1. Is Supported CPUID? ---> OK 2. Is Supported Extended CPUID? ---> OK 3. Is Supported NX/XD-bit? ---> Not supported. 4. Is Supported NX/XD-bit by OS? ---> OK 5. Is Enabled NX/XD-bit by OS? ---> Disable 6. Is working Hardware DEP? ---> NOT working... finish...
1. Is Supported CPUID? ---> OK 2. Is Supported Extended CPUID? ---> OK 3. Is Supported NX/XD-bit? ---> OK 4. Is Supported NX/XD-bit by OS? ---> OK 5. Is Enabled NX/XD-bit by OS? ---> Enable 6. Is working Hardware DEP? ---> NOT working... finish...
1. Is Supported CPUID? ---> OK 2. Is Supported Extended CPUID? ---> OK 3. Is Supported NX/XD-bit? ---> OK 4. Is Supported NX/XD-bit by OS? ---> OK 5. Is Enabled NX/XD-bit by OS? ---> Enable 6. Is working Hardware DEP? ---> Working! EXTRA. Try to Execute NX error? ---> push [Try NX!] finish...
checknx のアルゴリズム
- CPUID(0)が取得できるか?
取得できない場合、該当CPUか判断できないので終了。
- 拡張CPUID(8000-0000h)が取得できるか?
取得できない場合、NX-bit機能が実装されていないと判断し終了。EAXの値が 8000-0000h以上でない場合も終了。これはIntelの拡張CPUID使用方法に記載されていた方法です。拡張CPUIDをサポートしていない場合、CPUIDの最大INDEXの値が返却されるのが仕様です。
- 拡張CPUID(8000-0001h)が取得できるか?
取得できない場合、NX-bit機能が実装されていないと判断し終了。NX/XD-bitフラグが立っていない場合、NX-bit機能が実装されていないと判断しますが継続(Software DEP対応)。
- サポートOSかどうか?
XPSP2以降、もしくはWindows Server 2003 SP1以降ではない場合、DEPサポートOSではないと判断し終了。
- NX-bitが有効かどうか?
MSR(0xC0000080)の11bit目が立っている場合、NX-bitが働いていると判断し継続。立っていない場合、NX-bitが働いていないと判断するが継続。RDMSRが実行できなかった場合、とりあえず継続。
- メモリ保護が行われているか?
動的に取得した実行可能なメモリ上で実行を行い、問題がなければメモリを実行不許可に変更し、再度実行を試みます。実行不許可にしたメモリ上で実行した時に保護エラーが発生した場合、メモリ保護が働いていると判断しました。
- EXTRA. 実際にメモリ保護が行われるダイアログを見ますか?
6. の時点でメモリ保護が有効だった場合、実際にメモリ保護ダイアログを見るためのソフトウェアを用意しました(checknx自身がエラーを出力するのは嫌なので)。[TRY NX!]ボタンが有効になっているはずなので押してみてください。以下のダイアログが表示されれば、めでたく一般アプリケーションのメモリ保護エラーを拾える設定になっていることが確認できます。[メッセージを閉じる]ボタンを押すと、見慣れた「問題が発生したため、……」ダイアログが表示されます。[エラー報告を送信する]ボタンを押しても何ら改善される余地はありませんので、[送信しない]ボタンを選択してください。
checknxの要点
CPUID命令
CPUID(CPU Identification)命令は、i386系のCPUより実装された命令です。この命令を実行することで使用しているCPUの詳細情報を取得できます。
例えばSSE命令をサポートしているかどうかをCPUID命令から取得し、サポートしているCPUならばSSE専用の高速化ライブラリを使用するというような切り分けに用いられます。
今回は NX-bitをサポートしているかどうかの確認のため使用します。
__try{ _asm{ pushad mov eax, idx cpuid mov a, eax mov b, ebx mov c, ecx mov d, edx popad } }__except(EXCEPTION_EXECUTE_HANDLER){ bRet = FALSE; }
MSR(Model-Specific Register)
MSRは、Pentiumより実装されたCPU固有のレジスタです。これらのレジスタはCPU内部の特殊な制御を行うのに使用します。
RDMSR/WRMSR命令よりCPU内のMSRを読み書きを行います。一般的にMSRはCPUの動作を変更させることのできる重要なレジスタであることより、特権レベルでないと読み書きすることはできません。
今回は、実際に NX-bitによるメモリ保護を行っているかの確認に使用します。
SEH(Structured Exception Handling)
SEH(Structured Exception Handling、構造化例外処理)は、Windowsに標準で実装されているシステムレベルの例外処理です。
今回はSEHを用いてCPUID命令を直接実行しています。本来ならシステムがCPUID命令をサポートしているかどうかを、複雑な手順で確認してからCPUID命令を実行する必要があります。仮にCPUID命令をサポートしていないCPUで命令を実行した場合、CPUはシステムに対し一般保護例外を発行し、通常システムはその時点で実行を中断します。
SEHは、この一般保護エラーを例外処理として処理することができ、実行を中断させることなく処理を続けることができます。今回はこの機能を利用して、CPUID命令をサポートしているかどうかのチェックを省いています(手抜き?)。そもそもWindows XPを実行できるCPUを実装しているのであればCPUID命令は当然実装されているであろう、という考えもありました。
一般保護エラーを発生させることなく、CPUID命令をサポートしているかどうかを判断する処理については、Intelの資料AP-485が詳しいです。
ZwSystemDebugControl
ZwSystemDebugControlは、Windows NT系で実装されている非公開のNative APIです。
上記で説明したRDMSR命令ですが、通常の方法では実行できません。一般的に、MSRの制御を行うには特権レベルで動作するデバイスドライバを作成し、Read/Write/DeviceIoConrolを経由してMSR情報を取得します。ひよひよさん作の「CrystalCPUID」は、この方法を用いているようです。デバイスドライバによる取得方法は自由度が高いのですが、手順が面倒くさい、少しのミスがクリティカル(BSoD?)など、QuickHackには向いていません。
そこで今回は、ZwSystemDebugControlという非公開のNative APIを用いてMSR情報を取得することにしました。元ネタはSecurityFocusに投稿されたセキュリティ・ホール情報です。この投稿に対しMicrosoft社は「Debug権限を取得できた時点で相当危険な状態であり動作は仕様」との見解のようです。せっかくですので今回使わせてもらいました。ただし残念ながらこのAPIは、Windows XP以外では使用できないようです。Windows Server 2003 やWindows XP x64 Editionでは関数が正常に動作しないようでした。
Native APIは、Kernel modeとUser modeとの橋渡しに使用されるAPIで基本的には非公開情報のようです。
VirtualAlloc
VirtualAllocは、メモリ取得APIの一つです。メモリ管理領域に対し、明示的に実行可能/不可能を設定することができます。
当初、スタックを使って一般保護エラーを発生させようと試みたのですが、Release buildの時とDebug buildの時で挙動が異なり、安定して(?)メモリ保護によるエラーを発生させることができませんでした。そこで、まずVirtualAllocを利用して明示的に実行可能なメモリ領域を作成し、問題なくデータ実行が行えることを確認しました。次に、実行可能なメモリ領域を実行不可能なメモリ領域に変更し、再度、データ実行が行えるかでDEPが有効かどうかを判定しました。実行不可能なメモリ領域に変更してもデータ実行が行えた場合、DEPが働いていないと判断しました。
DEP環境下でデータ実行が必要である場合(動的コード生成を実行するアプリケーションなど)、明示的にデータ実行を可能にするフラグを立ててメモリ取得を行うことが推奨されています。
// Read/Write/Execute pf = (PBYTE)VirtualAlloc( NULL, 4, MEM_COMMIT, PAGE_EXECUTE_READWRITE ); if( pf == NULL ){ return FALSE; } *pf = 0xC3; // ret*4 *(pf+1) = 0xC3; *(pf+2) = 0xC3; *(pf+3) = 0xC3; pfunc = (PFUNC)pf; // SEH __try{ (*pfunc)(); }__except(EXCEPTION_EXECUTE_HANDLER){ // unknown... goto cleanup; } // Read/Write, NO Execute if( VirtualProtect(pf, 4, PAGE_READWRITE, &dwProt) ){ __try{ (*pfunc)(); }__except(EXCEPTION_EXECUTE_HANDLER){ // work NX-bit! bRet = TRUE; } }
まとめ
今回の調査により、ハードウェアDEPが動作している環境下では、常にNX-bitが使用されていることが分かりました。ただし、デフォルトの設定ではシステムに関わる部分に限ったメモリ保護であり、全てのアプリケーションが保護されるわけではありませんでした。これは、NX-bitによる一般保護エラーが発生した場合でも、システムに直接関わるモジュールではない場合、そのまま実行を続けることを許す処理になっていると考えられます。
データ保護設定を[次に選択するものを除くすべてのプログラムおよびサービスについてDEPを有効にする]に変更することより、全てのアプリケーションに対しデータ保護が有効になります。
ユーザーのシステム環境を守るためにも、ハードウェアDEPが有効である環境で安全に動作するアプリケーション開発が求められることになるでしょう。それに備えるためにも今後、全てのアプリケーションの開発を、ハードウェアDEPを有効にした環境で行うことが推奨されることになるかもしれません。
参考資料
インテル
- 日本語技術資料のダウンロード
日本語に翻訳された技術資料がまとまって置いてあります。ここから必要な資料を探しましょう。
- インテル 『AP-485 Intel Processor Identification and the CPUID Instruction』
CPUを認識するための資料です。日本語版もありますが、場合によってはかなり古いバージョンであることが多いので、おおよそ日本語版で理解できたらその後は英語版を使用した方が良いでしょう。
- インテル 『エグゼキュート・ディスエーブル・ビット機能』
XD-bitの機能について。
AMD
- AMD 『Microsoft Windows XP SP2に対応する拡張ウィルス防止機能(Enhanced Virus Protection)がデータを保護』
NX-bitの機能について。
- AMD64 Architecture Tech Docs
AMD64に関する仕様書が揃っています。日本語訳がほとんどないのは悲しいところです。
マイクロソフト
- マイクロソフト 『Windows Server 2003 Service Pack 1 (SP1) 早わかりガイド データ実行防止機能』、2005年4月
ハードウェアDEPの解説が詳しいです。
DEP に関する記事
- @IT 『Windows XP SP2で採用されたDEPの仕組み』 元麻布春男、2004年9月
- IT Pro 『Windows XP SP2のデータ実行防止機能「DEP」を調べた』 山口哲弘、2004年9月
Native API に関する情報
- SecurityFocus 『Multiple WinXP kernel vulns can give user mode programs kernel mode privileges』
Native APIの中にセキュリティ・ホールにかかわる問題があるとして報告のあった投稿記事(サンプルプログラム付き)。Microsoft社の見解ではこの機能は仕様とのこと。今回はRDMSR命令を実行するために利用しています。
関連書籍
- 『はじめて読むPentium マシン語入門編』 蒲地輝尚・水越康博 著、アスキー、2004年7月
Visual C++を用いたインラインアセンブラについての解説は役に立つでしょう。アセンブラの基礎を学びたい人にお勧め。
- 『アセンブリ言語の教科書』 愛甲健二 著、データハウス、2005年7月
最近出版されたアセンブラ本。これも初心者向けに良くできた書籍です。
- 『Windows NT/2000ネイティブAPIリファレンス』 Gary Nebbett 著、日向俊二 訳、ピアソンエデュケーション、2000年6月
Native APIについて記載のある唯一の書籍ではなかろうか。Native API に興味がある方はぜひ。