パスワードを忘れた? アカウント作成
10795825 journal
日記

Ab.の日記: COM hook

日記 by Ab.
Windows に標準で付属してきたりするような COM のフックについて少し考えてみました。

まず最初に思いつくのはこんな手法でしょうか。
  • フックする CoClass の実装しているインターフェースを wrapper として全て実装し
  • オリジナルの COM object を内部で保持し
  • IUnknown のメソッドに対しては適切に内部で処理し
  • それ以外のメソッドの呼び出しは wrapper としてオリジナルの COM object を呼び出し、その結果を返す(前後に処理を挟むなり場合によっては引き渡さずに戻ることもある)

ただし hook して wrap するクラスはオリジナルの CoClass が対応している Interface を 全て実装していなければならないという制約があります。
とは言っても完全に仕様が明らかになっていない場合もありますし、バージョンアップ等で知らない interface が追加される可能性もあります。
というわけで実際知らない IID が QueryInterface() で放り込まれた場合の処理を考えると、

  • E_NOTIMPL で拒否する
  • オリジナルの COM object に投げて、その interface pointer に関しては hook を放棄する

大体この2択になります。
(Aggregation で pUnkOuter を指定してオブジェクトを生成しておくのは aggregation がサポートされていない場合もあるし、 controlling unknown をちゃんと呼んでくれるかどうかは相手任せなので除外)
前者は場合によっては上手くありません。
アプリケーションの作りによっては当然有効なインターフェースポインタが帰ってくるものとして HRESULT も *ppv もチェックせずにメソッドを呼んでクラッシュしてしまうという危険性がありますし、エラーチェックをしていた場合でも特定の機能が使えなくなってしまうというのはよろしくありません。
余談ですが Windows7 の notepad で Ctrl+O を押すと出てくるファイルを開くダイアログ、まず COM の FileOpenDialog でダイアログを出そうとしますが、インターフェースの取得が失敗した場合等、 Win32API の GetOpenFileName() に fallback するようです。
意外に手堅い作りで驚きました。閑話休題。

後者ですがその(hookを放棄した) interface pointer からもう一回 QueryInterface() されると、hook先のオリジナルのオブジェクトのインターフェースが返される事になり、それが知っている interface でも既に hook が効かないパスでアクセスされてしまうことになります。
COM は基本的に QueryInterface() で別のインターフェースポインタを取得してそのポインタから QueryInterface() で元のインターフェースを要求すると、元のインターフェースポインタが帰ってくるように作るものなので、微妙に何処か変なことになる可能性もあります。
また、フックして余計な処理をしているパスとそれを通らないパスが同時に存在する事になるので、その事を前提にフック処理を実装しないと不具合が発生する可能性もあります。

で、 C/C++ というか高級言語で組んでるだけだとそこで終了ですが、 CPU から見ると COM Interface と言ってもスタックに引数積んで、this pointer 積んで、メソッドのアドレスを vtable から引いて、call してるだけです。
signature が未知のメソッドといえど this pointer をすげ替えてオリジナルのメソッドのアドレスに jump で飛び込むメソッドをアセンブラで実装しておけば汎用の hook wrapper として問題なく動作し、 hook を放棄せずにさらなる QueryInterface() (及び AddRef(), Release() の IUnknown メソッド)に対応できるはずです。
ただ、vtable は単なるポインタの配列なので対応するメソッドの個数の上限が事前に用意したテーブルのサイズで決まってしまい、それ以上のインデックスのメソッドが呼ばれた場合はクラッシュするという割と洒落にならない事態になります。
1k~4k個位メソッドを用意しておけばなんとかなるかな…まぁちょっとは覚悟しておけというところでしょうか。
などと考えていたら、この間 VC++ のCOM用のライブラリである ATL のヘッダファイルを眺めていたら MS がまさに完璧に同じことをしていてひっくり返りました。 まぁちょっと考えれば誰でも思いつく類のアイデアですが。
デバッグ用らしい ATL::_QIThunk というクラス(というか構造体)ですが VS2012 の版では用意されているメソッドのテーブルは1024個でした。
どうして1024になっているのか検索して見ると、どうやら Windows2000 未満の環境では idl コンパイラの midl に /Oicf オプションを付けた際にメソッド数の上限を1024と定めているようで、そこから手繰ってみると marsharing のために生成する情報で制限が出てきているっぽいですね。
そういう訳で余裕を見て4kエントリ位用意しておけば良さそうな感触ではあります。

(実はあと少なくとももう一個方法があるのですが、秘密というかもう少し色々実験したり考察してみてから)

この議論は賞味期限が切れたので、アーカイブ化されています。 新たにコメントを付けることはできません。
typodupeerror

皆さんもソースを読むときに、行と行の間を読むような気持ちで見てほしい -- あるハッカー

読み込み中...