========================================================================== DCHook sample program ========================================================================== Introduction ------------------------------------------------------------- DCHookは、Windowsにおいて任意のWindowの任意の座標位置から そこに描画されているテキストを拾ってくるプログラムです。 ちょっとやってみたら簡単に動いたので、公開することにしました。 このプログラムの応用としては、 ・任意のウィンドウのテキストを拾う ・全ウィンドウのテキストをgrepする ・カーソル位置のテキストを拾う といった一風変わったことができます。 このプログラムには参考として、DokoPopというサンプルプログラムを 添付してあります。 install ------------------------------------------------------------------ 圧縮ファイルを適当なところに解凍してください。 ディレクトリ付きで解凍する必要があります。 添付ファイル ------------------------------------------------------------- targetになるもの: dchook.dll (NT/2K版 or 9x版) hk95d.vxd (9x専用) dchooktest.exe (NT/2Kと9x8共用) 必要な開発環境 ----------------------------------------------------------- C++ Builder 5 Visual C++ 6.0 Windows Device Driver Kit (DDK) for Windows 98 作成方法 ----------------------------------------------------------------- -------------------------------- dchook.dll, dchook64.dll -------------------------------- 必要な環境: Visual C++ 2008 作成方法: DCHook.6.0.slnを開き、ビルド または nmake -fdchook.mak -------------------------------- DCHook64.exe -------------------------------- 必要な環境: Visual C++ 2010 作成方法: dchk64.slnを開く ビルド - 構成マネージャーよりアクティブソリューションプラットフォームを x64 に切り替える -------------------------------- hk95d.vxd -------------------------------- 必要な環境 Visual C++, DDK for Win98 変更箇所: makefile INC32をDDKのroot pathに(e:\98ddkなど) 作成方法: nmake ※この実行前にVisual-C++およびDDKの環境が設定されている必要があります。  バッチファイルで環境設定するようになっている場合は、  作成前にバッチファイルを実行してください。  (vcvars32.bat、setenv.bat e:\98ddk といった感じで  →詳しくはVisual-C++、DDKに付属の説明書を参照) -------------------------------- dchooktest.exe -------------------------------- 必要な環境: C++ Builder 5 変更箇所: 特になし ※DEBUGをdefineすると、DEBUG用の動作になります。 作成方法: dchooktest.bprをC++Builderで開く メニューから Project | 再構築 で作成 ★注意点: DCHookTest.exeがLoadするDLLは、 DKPPHK.DLL -> DCHOOK.DLL の順番で先に見つかった方をloadする仕様になっています。 DEBUGをdefineしたときは、DCHOOK.DLLのみをloadしようとします。 プログラム説明 ----------------------------------------------------------- 一番厄介なものは、dchook.cppでしょう。 試行錯誤しながらも短時間で一気に書き上げたものですので、 かなり汚くなっています。 #defineによるswitchには、色んな組み合わせがありますが、 現状以外の設定にすると正常に動作しなかったり、 compileさえもできないはずです。だったら、整理しなさい! と言われそうですが、今後必要となるところがあるかもしれないので、 そのままにしてあります。 動作原理としては、テキストを取得したいwindowに対して 再描画命令を送り、再描画中のテキスト描画関数の内容を 横取りすることで拾ってきます。 最初作り始めた頃は、WindowsからDCを渡している関数をhookし、 DCをmetafileで再生すれば簡単じゃないか?と思ってやってみました。 そうしたらあっけなく動いたのですが、別のapplicationでやってみると うまく動かないことがありました。 原因は、CreateCompatibleBitmapでbitmapを作成し、そこへ一旦描画 してから、BitBltでwindowに貼り付けるapplicationがあったからです。 そうなると、DCだけではテキスト描画情報が得られませんので、 必要なtext描画関数をhookしてやる必要があります。 (ほかにもBeginPaint()で渡されるDCではなく、GetDC()で 得たDCに対して描画しているapplicatonに対してもうまくいきませんでした) この前にこのhookの方法ですが、NTと9xでは異なります。 NTは各applicationが独立のmemory空間であり、user32.dll,gdi32.dllも application毎のmemory空間に存在しているのですが、9xでは、 いきなり共有memoryへ行ってしまっているので、簡単にはhookできません。 APIのhook方法は、アスキーから出版されている「Advanced Windows」 を参考にしました。 http://www.amazon.co.jp/exec/obidos/ASIN/4756138055/pdichomepage-22 ここにAPIフックの説明が詳細に記述されています。 しかし、この方法を知るまでは Hook先のアドレスを直接書き換えるという 危険な方法をやっていました。つまり Advanced Windowsに記述されている 1番目の方法ですね。NT系のOSならほぼ問題なく動作するのですが、 9x系は例外がどうしても発生してしまいます。 ということで、この本の2番目の方法で安定して動作するように なりました。しかし、この本のままではDokoPop!で効率よく 動作してくれないので、かなり書き換えてあります。 (ということで著作権表示も省略したりして・・・) 書き換えた、というより、今まで自分が書いたAPIHOOK構造体を 変更した、というほうが正しいかもしれません。 APIフックの解説はこの本に譲ります。 ですが、せっかく1番目の危険な方法でいくつか得た情報があるので、 旧バージョンで使用していたフック方法の情報を書いておきます。 ---------- 古い話 ---------- ちなみに、NTではUSER32.DLL,GDI32.DLLのGetProcAddress()は applicationによって異なります。しかし、9xでは同じです。 先ほどもちらっと書いたように、NTのapplicationは個別のmemory空間で USER32.DLL,GDI32.DLLも異なる空間に存在しているからです。 ところが9xは共有していて、しかもjumpしたらすぐにkernel modeに 入っているようです。(NTはある程度user modeでやっている感じ) さらにその先はどうなっているかというと・・・という話は このプログラムのscope外なのでまたの機会に譲るとして、 ここで9xに対してちょっと嫌な予感がよぎります。 NTの場合、entry pointを書き換えてもそのapplication固有の entry pointであるため、その間にtask switchingが発生しても 問題ないのですが、9xでは、entry pointを書き換えた直後に task switchingが発生したら、(来て欲しくない)ほかのapplicationが そのhookを伝ってやってくる・・・最悪entry point書き換え中に switchingが発生すると・・・ちょっと怖いです。 でも、実際はそのようなケースは起こりにくく、実際そういう 問題は発生していません。ですが、一応プログラムでは そのための対策をしてあります。 (このへんの理屈も書いた方がいいのですが・・・省略) entry pointの書き換えですが、NTの場合は 通常のapplicationと同じですので、WriteProcessMemory関数で code領域を書き込みができます。しかし、9xは 共有memoryであり、そこはwrite protectされています。 このprotectionを解除するには通常のWindows APIには 無いようなので(あったら教えて欲しい)、わざわざ memoryを書き換えるだけのためのVxDを作りました。 VxDならkernel modeなのでなんでもできてしまいます。 (このへんが先ほどのtask switchingの話と絡んでいますが) VxDはdynamic loadingで作ってあります。 このVxDで注意しなければならないのは、CreateFile()関数で VxDをloadしていますが、二度CreateFile()はできないところです。 従って、一番最初の初期化のときだけ、CreateFile()を行い、 あとはずっとVxDを常駐させたままにしておく必要があります。 一応API Hookの話はこのくらいにしておきます。 API Hookをするapplicationはほかにもありますが、 そういうapplicationはこれと同じ方法を使用しているのでしょうか? MSDNにもapimon(だったかな?)というtoolが付属していますが、 どうなんでしょう? ---------- 以上、古い話 ---------- 話はずっと前に戻って、BitBlt対策の話です。 このBitBltで描画する方法を調べてみると、window全体ではなく、 bandingによって、windowを複数のbitmapに分割してBitBltしている ようです。従って、BitBlt APIもhookしてやり、bitmapへtextを 描画したときの座標と、BitBltしたときのdestination pointの offsetを考慮してtext座標を計算してやる必要があります。 以上、textをgetできたら、指定座標(sampleではmouseのcursor位置)に textがあるかどうか、という判定を行います。 hitした場合は、そのtext全体と、text上のbyte offsetを WM_COPYDATA messageで初期化の時に登録してある windowへSendMessage() します。(WM_COPYDATAはSendMessageでないといけない) そして、DCHookTest.exeがそのmessageを受け取り、DCHookTest.exeが textをほかのapplication(PDIC)へDDEで渡しています。 DCHookTest.exeがやっていることはそれがほとんどで、 dchook.dllにくらべれば遙かに楽な仕事なのです。 DLLでは常識ですが、dataは基本的にprocess固有の値を持ちます。 ここで注意しなければならないのが、WindowsHookのhook dllは、 WindowsHookでセットした後、foreground applicationに 切り替わったときに初めてattachされます。 (つまり、WindowsHookした時点ではHookしたprocessにしか attachされていない) しかも、attachしたときには、別のapplicationのprocess空間に 居ますので、WM_COPYDATAの送り先の情報がどこにもありません。 ファイル/registryに保存して読み込む、なんていう方法もあり ますが、dchook.defでsharedによって、dll間で共有する data segmentを作成する方法があります。 ここにWM_COPYDATA送り先のhandleを保存しておけば、このような 問題は解決できます。 それともう一つやってみてわかったのですが、9xでは Unhookをすると、すぐにすべてdetachされるのですが、 NTでは、これまたforegroundに切り替わったときにしか detachしてくれません。そこで、dchookでは強制的に detachさせるためにmessageをbroadcastさせています。 実際はそこまでやる必要性はないのですが、開発時に memoryを解放させるために色んなapplicationに切り替えるのが 嫌だったので・・・BroadcastMessage以外に良い方法があるのかな? プログラム解説 ----------------------------------------------------------- DBW() デバッグ用の関数です。拙作のdbgmsg.exeへ文字列を出力しますが、 ほかのdebug stringを補足するdebug string monitorで使えるように DBW()を書き換えたほうが良いでしょう。(OutputDebugString()など) popenable.h, popdisable.h これらは「右クリック検索を有効にしました」というメッセージウィンドウの リージョン情報です。(丸みのあるウィンドウ) このリージョン情報は、bmp2rgnというツールでpopenable.bmp, popdisable.bmp を変換したものです。bmp2rgnはフリーウェアです。 著作権 ------------------------------------------------------------------ 再配布、転載、複製、改変、販売等はすべて自由です。一切の制限を設けません。 ただし、以下のことはご留意ください。 ・このソフトウェアによって生じる一切の損害責任を負いません。 ・このソフトウェアに関して筆者はいかなる義務も負いません。 ・再配布されたものに関しては一切関知しません。 履歴 -------------------------------------------------------------------- >> Ver.1.12 << * 辞書が大きい場合に、PDIC起動後の最初のポップアップができないときがあった >> Ver.1.11 << ・Unicode版対応のために内部処理を変更 >> Ver.1.10 << ○DokoPopでポップアップしたウィンドウ上でさらにポップアップする ことが可能に(連続ポップアップ)(PDIC for Win32はVer.4.50以上が必要) ○日本語混在の文章でも正しく動作するようにした。  (設定ダイアログの”日本語を無視する”で切り替え可能) >> Ver.1.02 << * DKPP.EXEのバージョン設定を間違っていた >> Ver.1.01 << * Windows9xでポップアップしない場合があった(IEなど) ・それに対処したため、Windows95は動作対象外に * 右クリック検索切り替えでリソースリークが発生していた * ヒットしない単語があるとメモリリークが発生していた >> Ver.1.00 << * Windows9x/Me系のOSでの動作を安定化した * 辞書グループ指定を正常に動作するようにした 【注意】この機能はPDIC for Win32 Ver.4.40からの対応になります ・検索のキーの組み合わせをカスタマイズできるようにした ・ファイル構成を変更 ・連続してポップアップ検索ができるようにした ・ほか表示の変更など >> Ver.0.98 << ○マウス右クリックのみでポップアップを可能に  (Ctrl+Alt+右クリックでON/OFFできます) * ”辞書グループが開けません”と出ることがあった * 最小化で起動した場合、タスクバーにアイコンが残っていた >> Ver.0.97 << * 辞書グループの変更ができなくなっていた * IEでヒットしないことがあった >> Ver.0.96 << * PDICを自動起動できなかった(読み込むregistryを間違っていた) * どこポップを最小化で起動した場合、スプラッシュウィンドウが消えなかった >> Ver.0.95 << ?? >> Ver.0.94 << * スプラッシュを非表示にした場合の不具合修正 >> Ver.0.94 << * 起動時DLL初期化失敗(Win98) * 日本語混じりの文でテキスト抽出位置がずれていた(Win98) >> Ver.0.93 << ・起動スプラッシュ変更 ・スプラッシュ表示・非表示 ・スプラッシュビットマップのカスタマイズ ・インストーラ追加 * メモ帳で異常終了(NT) * 日本語混じりの文でテキスト抽出位置がずれていた >> Ver.0.92 << ・安定性向上 >> Ver.0.90 << ・最初の公開