2007年09月21日
Winsock API をフックする際に注意すべきこと
■ はじめに
このブログにはこれまでに何度か Win32 API のフックに関する記事を書いてきました。 API フックを行うには通常のプログラミングとは少し異なる知識と技法が必要となることもあって 記事の大半は技術的な話題が中心でしたが、今回は本題に入る前にちょっと別の視点で考えてみることにします。
そもそも、「API をフックする」とはどういうことでしょう?
また、それによって一体なにができるのでしょう?
このブログにはこれまでに何度か Win32 API のフックに関する記事を書いてきました。 API フックを行うには通常のプログラミングとは少し異なる知識と技法が必要となることもあって 記事の大半は技術的な話題が中心でしたが、今回は本題に入る前にちょっと別の視点で考えてみることにします。
そもそも、「API をフックする」とはどういうことでしょう?
また、それによって一体なにができるのでしょう?
Windows 上で動作するアプリケーションのほとんどは、Windows に用意されている API をプログラムの中から呼び出すことによって
その機能を実現しています。
たとえば、画像の表示や音楽の再生、ファイルの読み込みや保存、メールの送受信や Web サーバへのアクセス、 こういった普段何気なく利用している機能は、最終的には Windows API を利用しています。 言い換えれば、API をまったく使わずに Windows プログラムを作成するのはきわめて難しいことなのです。
「API フック」とは「プログラムが特定の API を呼び出す際にそこに割り込むこと」を指します。
プログラム自身は自分が呼び出しているのは目的の API だと思い込んでいるわけですから、API フックを利用すれば、 そのプログラムが特定の API との間でやりとりしている情報のすべてにアクセスすることが可能となります。
そして、一旦 API 呼び出しをフックすれば、その処理の過程で本物の API を呼び出すこともできますし、もちろんまったく別の機能を提供することもできます。
つまり、API フックを利用すれば、既存のプログラムからの API 呼び出しを監視することが可能であり、そこに新しい機能を追加したり、あるいは機能の一部を変更したりすることができるようになるのです。
たとえば、特定のあて先にメールを送る場合に自動的に自分のアドレスを Cc: に加えるといった機能をメーラに追加することもできますし、文書保存時に元文書のバックアップ機能を持たないワープロソフトにそれを追加することも可能です。
さらに変わった使い方としては、特定のプログラムの起動を禁止したり、特定のプログラムの認識する日付情報を操作したり、場合によってはプログラムのバグをフックで吸収してしまうこともできるでしょう。
いかがでしょう?これらはほんの一例にすぎませんが、API フックを活用すれば、プログラム本体を変更することなくこういった興味深いことを実現できるのです。
そして、いま私たちが注目しているのはネットワーク通信のフックです。 今日の状況では「コンピュータを使う=ネットワークを使う」と言っても過言ではありませんし、手元のいろいろなアプリケーションのネットワーク送受信に自分のプログラムコードをを介入させることができればそのメリットは大きいでしょう。
ところが、Windows 標準のソケット通信インターフェイスである「Winsock」の API をフックするためには、知っておかなければならない重要なノウハウがあります。これは独自の調査により判明したもので、とりわけ、インポートアドレステーブル(IAT) 書き換えの手法によりフックを実装する場合にはこの点を見過ごしていると おそらく Winsock API を適切にフックすることはできないでしょう。
少々前置きが長くなってしまいました。これが今回の本題です。
たとえば、あるプログラムが呼び出している Winsock API の内、WS2_32 の send と recv をフックするとします。
この場合、何らかの形で send, recv に置き換えるコードを用意しておき、プロセス空間の各モジュールの IAT をチェックして send, recv に該当するアドレスがあればそれを書き換えるわけですが、上に書いた「基本となる話題」の内容に準じ、このふたつの API に加えて LoadLibrary* 系と GetProcAddress の各 API をフック対象とするフックコードを用意して実際に試してみることにします。
下の図は、このフックコードをインターネットエクスプローラ(IE) のプロセスへ注入した結果です。
(※Windows Vista で Internet Explorer 7 を実行時の表示例)
どうやら通信処理に失敗してしまっているようです。なぜこうなってしまうのでしょう?
実は、この状況に至るまでに Winsock 初期化のために呼び出された WSAStartup API が WSASYSNOTREADY (10091) の エラーを起こしているのです。WSAStartup が正常に終了しないと Winsock API は正常に動作しませんね。
ここで WSAStartup がエラーを起こしている原因は、同 API の内部処理と、手元で用意したフックコードとの衝突にあります。
WSAStartup は処理の過程において、自分自身の格納されている DLL のエクスポート情報を 参照し、すべてのエクスポート API のシンボル名について GetProcAddress API を使ってアドレスの取得を試みます。 そして、そのそれぞれについて実際のエクスポートアドレスとの比較を行い、ふたつのアドレスの一致しないものを見つけると エラーと判定する処理を行っています。
WSAStartup のこの部分の逆アセンブルコードを以下に示します。
※ WS2_32.dll は ver 6.0.6000.16386 (2006/11/02 18:46) です
つまり、send, recv が正しくフックされ、あわせて冒頭の要領で GetProcAddress API が適切にフックされた状況だと、 WSAStartup が上記のアドレスチェックを行う際に呼び出される GetProcAddress は実はフックされた代替コードであるため、 send, recv のアドレスが照会されるとそれぞれに対応する代替コードのアドレスが返されることになり照合エラーが発生する、というわけです。
あくまでも現時点での実用上のノウハウであることを記憶にお留め下さい。
※ Windows2000, XP および現在の Vista での Winsock1.1/ 2.0 においては、本件に関する所作は共通です
たとえば、画像の表示や音楽の再生、ファイルの読み込みや保存、メールの送受信や Web サーバへのアクセス、 こういった普段何気なく利用している機能は、最終的には Windows API を利用しています。 言い換えれば、API をまったく使わずに Windows プログラムを作成するのはきわめて難しいことなのです。
「API フック」とは「プログラムが特定の API を呼び出す際にそこに割り込むこと」を指します。
プログラム自身は自分が呼び出しているのは目的の API だと思い込んでいるわけですから、API フックを利用すれば、 そのプログラムが特定の API との間でやりとりしている情報のすべてにアクセスすることが可能となります。
そして、一旦 API 呼び出しをフックすれば、その処理の過程で本物の API を呼び出すこともできますし、もちろんまったく別の機能を提供することもできます。
つまり、API フックを利用すれば、既存のプログラムからの API 呼び出しを監視することが可能であり、そこに新しい機能を追加したり、あるいは機能の一部を変更したりすることができるようになるのです。
たとえば、特定のあて先にメールを送る場合に自動的に自分のアドレスを Cc: に加えるといった機能をメーラに追加することもできますし、文書保存時に元文書のバックアップ機能を持たないワープロソフトにそれを追加することも可能です。
さらに変わった使い方としては、特定のプログラムの起動を禁止したり、特定のプログラムの認識する日付情報を操作したり、場合によってはプログラムのバグをフックで吸収してしまうこともできるでしょう。
いかがでしょう?これらはほんの一例にすぎませんが、API フックを活用すれば、プログラム本体を変更することなくこういった興味深いことを実現できるのです。
そして、いま私たちが注目しているのはネットワーク通信のフックです。 今日の状況では「コンピュータを使う=ネットワークを使う」と言っても過言ではありませんし、手元のいろいろなアプリケーションのネットワーク送受信に自分のプログラムコードをを介入させることができればそのメリットは大きいでしょう。
ところが、Windows 標準のソケット通信インターフェイスである「Winsock」の API をフックするためには、知っておかなければならない重要なノウハウがあります。これは独自の調査により判明したもので、とりわけ、インポートアドレステーブル(IAT) 書き換えの手法によりフックを実装する場合にはこの点を見過ごしていると おそらく Winsock API を適切にフックすることはできないでしょう。
少々前置きが長くなってしまいました。これが今回の本題です。
■ 基本となる話題
はじめに、インポートアドレステーブル(IAT) の書き換えによる API フックを実装する際に 必要となる要素を簡単に整理してみます。- 対象プログラムの起動時に、そのプロセスのメモリ空間へ自作のコードを注入する
- 自作のコードは対象プログラムのプロセス内で以下の処理を行う。
・プロセスにロードされているすべてのモジュールの IAT を走査し、フック対象とする API のアドレスを発見したら 代替コードのアドレスに書き換える
・プログラム実行中にコードからロードされるモジュールの IAT も上記と同様に操作することを目的に Kernel32 の LoadLibyray* 系 API もあわせてフックする。代替コード内では本物の API を呼び出した後で IAT 処理を行う
・プログラム実行中にコードからフック対象 API の本物のアドレスが取得されるのを防ぐことを目的に Kernel32 の GetProcAddress API をあわせてフックする。代替コード内では、照会された API がフック対象のものであれば 代替コードのアドレスを返し、そうでなければ本物の API の実行結果を返す。
■ Winsock API フックでの注意点
さて、本題の Winsock API のフックについてです。たとえば、あるプログラムが呼び出している Winsock API の内、WS2_32 の send と recv をフックするとします。
この場合、何らかの形で send, recv に置き換えるコードを用意しておき、プロセス空間の各モジュールの IAT をチェックして send, recv に該当するアドレスがあればそれを書き換えるわけですが、上に書いた「基本となる話題」の内容に準じ、このふたつの API に加えて LoadLibrary* 系と GetProcAddress の各 API をフック対象とするフックコードを用意して実際に試してみることにします。
下の図は、このフックコードをインターネットエクスプローラ(IE) のプロセスへ注入した結果です。
(※Windows Vista で Internet Explorer 7 を実行時の表示例)
どうやら通信処理に失敗してしまっているようです。なぜこうなってしまうのでしょう?
実は、この状況に至るまでに Winsock 初期化のために呼び出された WSAStartup API が WSASYSNOTREADY (10091) の エラーを起こしているのです。WSAStartup が正常に終了しないと Winsock API は正常に動作しませんね。
ここで WSAStartup がエラーを起こしている原因は、同 API の内部処理と、手元で用意したフックコードとの衝突にあります。
WSAStartup は処理の過程において、自分自身の格納されている DLL のエクスポート情報を 参照し、すべてのエクスポート API のシンボル名について GetProcAddress API を使ってアドレスの取得を試みます。 そして、そのそれぞれについて実際のエクスポートアドレスとの比較を行い、ふたつのアドレスの一致しないものを見つけると エラーと判定する処理を行っています。
WSAStartup のこの部分の逆アセンブルコードを以下に示します。
※ WS2_32.dll は ver 6.0.6000.16386 (2006/11/02 18:46) です
つまり、send, recv が正しくフックされ、あわせて冒頭の要領で GetProcAddress API が適切にフックされた状況だと、 WSAStartup が上記のアドレスチェックを行う際に呼び出される GetProcAddress は実はフックされた代替コードであるため、 send, recv のアドレスが照会されるとそれぞれに対応する代替コードのアドレスが返されることになり照合エラーが発生する、というわけです。
■ ではどうする?
この件への対策のひとつとして次の方法が考えられます。- WSAStartup API をあわせてフック対象とする
- WSAStartup の代替コードの冒頭で、GetProcAddress の代替コードに対しフックの一時停止を指示する
- WSAStartup の代替コード内で本物の WSAStartup を呼び出す
- 本物の WSAStartup の処理が終わったら GetProcAddress フックの一時停止を解除する
■ 念のために
ここに記載した内容はマイクロソフト社が公開しているものではなく、独自に調査を行った結果に基づくものです。 したがって、今後の Winsock ライブラリにおいての所作がこれと同一であるという保証はどこにもありません(※)。あくまでも現時点での実用上のノウハウであることを記憶にお留め下さい。
※ Windows2000, XP および現在の Vista での Winsock1.1/ 2.0 においては、本件に関する所作は共通です
トラックバックURL
この記事へのトラックバック
1. フックフック [ とあるIT系求職者の日記(仮称) ] 2010年10月26日 21:57
WinsockAPIをフックできない件、ググったら一発じゃん。 http://d...