パスワード閲覧ソフトというものがあります。例えば、有名なもので「パスみえ2000 1.00」があります。vectorなどで検索するとざっと4つほどみつかりますが、キーワードを変えるともっとあるかもしれません。しかし、それらはすべてソースコードが公開されてなかったので、仕組みを調べようにも、どうやっているのか分かりません。多分、この仕組みを知りたいと思ってる人は少なからずいるのではないか、と思い、今回の記事を書いてみました。
実際、ものすごく簡単な仕組みなので、どこかのサイトに紹介されててもよいと思ったのですが、検索してみると無いのですよね(もし見つけたらkenji@ruffnex.oc.toへ教えてください^^;)。なので自分で書いてみました。
ということで、今回はこのツールを実際に作ってみます。環境はWindowsXP + VC++.NETですが、Borland C++ 5.5.1でも確認しています。
追記:Codeguruに「Peeking into Password Edit & Internet Explorer - Super Password Spy++」というページがあります。このページでは、DLLを他プロセスへ注入してパスワードのフックを行っています。Internet Explorerのパスワード入力欄のデータの取得も可能なようです、すごい(汗)。でも、Firefoxは無理なようでした(^^;。ソースコードも公開されていますので、一見の価値ありです。
このようなツールって、とても難しいことをやっているんじゃないかな、と思う方もいるかもしれませんが、本当は驚くほど簡単な仕組みです。実現方法はいろいろとあると思いますが、ここではおそらくもっとも簡単であろう方法を紹介します。必要な技術は、マウスキャプチャーだけです。
まずマウスキャプチャーを利用して、自分以外のWindowのハンドルを取得します。パスワード閲覧ソフトを作るならば、その「****」という文字が書かれてあるWindowのハンドルを取得するわけです。
そしてそのWindowハンドルを利用して、「****」というパスワードが書かれてあるWindowへPostMessageでパスワード表示解除の命令を送信します。これで、無事パスワードが表示されることになります。
通常、マウスポインタが自分のWindow内にあるときは、マウスに関するあらゆるメッセージを受け取ることができます。しかし、マウスポインタがWindow外に出るとメッセージを受け取ることができません。これは当たり前のことですよね。もちろんフックなどを使うと受け取ることができますが、それはまた別です。
マウスキャプチャーとは、マウスポインタがWindowの外にでても、メッセージをキャプチャーしてくれる技術です。しかしフックと違って、かなり制限がありますので、本格的に別のWindowに送られるメッセージをゲットして、何かをやらかしたい場合には、フックを使うことをお勧めします。
マウスキャプチャーについてはお馴染みの猫でもわかるプログラミングの101章をみてください。あとWisdomSoftのマウスキャプチャーの章でもかなり詳細な解説がされています。ちなみに私はこの2つのサイトで、Windowsプログラミングを覚えました(笑)。
ソースコードは以下にアップしました。
./capture.cppソースコード解説としていますが、とりあえずコンパイルして実行してみてください。以下にWindowsXPのBorland C++ 5.5.1でコンパイルした例を示します。
C:\>bcc32 -W capture.cpp Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland capture.cpp: 警告 W8057 capture.cpp 49: パラメータ 'hPrevInstance' は一度も使用されない (関数__stdcall WinMain(HINSTANCE__ *,HINSTANCE__ *,char *,int) ) 警告 W8057 capture.cpp 49: パラメータ 'lpCmdLine' は一度も使用されない (関数 __stdcall WinMain(HINSTANCE__ *,HINSTANCE__ *,char *,int) ) Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland C:\>
警告はでますが、ちゃんとコンパイルは通るはずです。Windowsプログラムなので、コンパイルオプションに「-W」をつけています。では、実行してみてください。ちゃんとWindowが表示されたら成功です。

図1 capture.exeの実行例
試しに、「****」と表示されるところにマウスをドラッグしてみてください。おそらく、ちゃんとパスワードが見えるようになるはずです。ちなみにブラウザに表示されるパスワード入力欄には、対応していません。
では早速ソースコードを読んでみます。
LRESULT CALLBACK WndProc(HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
{
switch (message)
{
case WM_PAINT:
OpningPrintString(hWnd);
break;
WM_PAINTメッセージが来たときの処理です。Windowに文字列を表示するための処理を受け持つ関数OpningPrintStringを呼び出しています。
void OpningPrintString(HWND hWnd)
{
RECT rc;
GetClientRect(hWnd, &rc);
rc.left += 40, rc.top += 40, rc.right -= 40, rc.bottom -= 40;
PAINTSTRUCT paint;
HDC hdc = BeginPaint(hWnd, &paint);
DrawText(hdc, _T(
"このWindow領域をクリックするとマウスカーソルが変わるので、\n"
"クリックしたまま、パスワードが書かれてある領域に、\n"
"ドラッグしてください"),
-1, &rc, DT_WORDBREAK);
EndPaint(hWnd, &paint);
}
パスワード閲覧とは全然関係ない処理ですが、解説します。まずGetClientRectでクライアント領域の座標を調べます。そして表示すべき座標を適当に決めます(ここではそれぞれ40pxの幅を確保しています)。
次にBeginPaintとDrawText、そしてEndPaintというテキスト描画に関する一連の処理を行っています。
DrawTextの引数は、1番目がBeginPaintの戻り値、2番目が描画するテキスト、3番目が描画するテキストの長さ(ですが、ここを-1にすると勝手にテキストの長さを計算してくれます)、そして4番目が描画する領域、5番目がフラグです。5番目のフラグは、DT_WORDBREAKとすると、適切な改行をしてくれます。
case WM_LBUTTONDOWN:
SetCapture(hWnd);
SetCursor(LoadCursor(NULL, IDC_CROSS));
break;
WndProcに戻ります。ここでSetCaptureを呼びます。つまりマウスの左ボタンが押されたらマウスキャプチャーを開始するわけです。さらにキャプチャー中であることを示すためにSetCursorでマウスポインタの画像を変更します。キャプチャー中ならば、自分のWindow外のマウスメッセージを取得できるので、そのままパスワードが書かれてあるWindowにマウスポインタを持っていってもらいます。
case WM_LBUTTONUP:
if( GetPasswdWindow(hWnd, lParam) )
MessageBox(hWnd, _T("GetPasswdWindow"), _T("Error"), MB_OK);
ReleaseCapture();
SetCursor(LoadCursor(NULL, IDC_ARROW));
break;
マウスの左ボタンが離されたら、キャプチャーを終了します。キャプチャーの終了はReleaseCaptureですね。SetCursorでマウスポインタの画像を元に戻します。パスワードの閲覧を受け持っているのは、GetPasswdWindowという関数です。
BOOL GetPasswdWindow(HWND hWnd, LPARAM lp)
{
POINTS pts = MAKEPOINTS(lp);
POINT pt = { pts.x, pts.y };
ClientToScreen(hWnd, &pt);
HWND hTarget = WindowFromPoint(pt);
if(hTarget == NULL)
return TRUE;
PostMessage(hTarget, EM_SETPASSWORDCHAR, (WPARAM)0, (LPARAM)0);
InvalidateRect(hTarget, NULL, TRUE);
return FALSE;
}
GetPasswdWindow関数は、マウスの左ボタンが離されたときに呼ばれます。ちなみにこの関数が実行されているときは、まだマウスキャプチャー中なので自分のWindow外のメッセージもゲットできます。つまりWindowの外、内、に関係なしにptにマウスの位置が入ることになります。
ClientToScreenは、指定されたWindowのクライアント座標からスクリーン座標に変換します。この場合の指定されたWindowとは、パスワードの文字列をもったWindowです。そしてその座標を、スクリーン座標に変換します。
WindowFromPointは、指定した座標にあるWindowのハンドルを戻り値として返します。ここまでの流れとして、まずマウスポインタの位置を取得します。そして、その位置をスクリーン座標に変換します。そのスクリーン座標からWindowFromPointを使って、その位置にあるWindowハンドルを取得します。
そして、取得したハンドルへPostMessageでパスワードスタイルを解除させます。最後に、そのWindowをInvalidateRectで更新します。
これでプログラム解説は終わりです。
マジで簡単でした...(^^;
さて、一応これで終了なのですが、おまけと題して、ちょっとした遊びをやってみようと思います。
例えば、GetWindowTextを使うと、WindowハンドルからWindowのタイトルを取得することができます。GetPasswdWindow関数を以下のように変更します。
BOOL GetPasswdWindow(HWND hWnd, LPARAM lp)
{
POINTS pts = MAKEPOINTS(lp);
POINT pt = { pts.x, pts.y };
ClientToScreen(hWnd, &pt);
HWND hTarget = WindowFromPoint(pt);
if(hTarget == NULL)
return TRUE;
TCHAR buf[1024];
GetWindowText(hTarget, buf, 1024);
MessageBox(hWnd, buf, "報告", MB_OK);
return FALSE;
}
こうして、マウスポインタをWindowのタイトルバーに当てると、他のWindowのタイトルを取得することができます。
GetWindowTextの他にWM_GETTEXTを使う手もあります。これを使うとエディットボックスに入力されている文字列なども取得することができます(でもパスワードスタイルがかかってたら無理ですよ)。
BOOL GetPasswdWindow(HWND hWnd, LPARAM lp)
{
POINTS pts = MAKEPOINTS(lp);
POINT pt = { pts.x, pts.y };
ClientToScreen(hWnd, &pt);
HWND hTarget = WindowFromPoint(pt);
if(hTarget == NULL)
return TRUE;
TCHAR buf[1024];
SendMessage(hTarget, WM_GETTEXT, 1024, (LPARAM)buf);
MessageBox(hWnd, buf, "報告", MB_OK);
return FALSE;
}
SendMessageでそのWindowへメッセージを送っています。WM_GETTEXTを指定すると、そのテキストを教えてくれます。
マウスキャプチャーは以外と面白いので、いろいろと試してみてください。フックほど強力ではありませんが、シンプルなので手軽にテストができて楽しめると思います。
さて、いかがだったでしょうか。意外に簡単で面白そうなネタだったので、ちょっと書いてみました。私自身マウスキャプチャーの勉強にもなりましたし、あんまりこのようなものを解説しているページもなかったので。ソースコードは、VC++.NETで書いたのですが、なんかBCCでもコンパイルが通ってちょっとびっくりしました。マウスキャプチャーは結構楽しいので、他にもいろいろと試してみて下さい。