はじめに
Internet Explorerでは、認証パスワードの記憶やオートコンプリートを有効にしていると、それぞれのデータがコンピュータに保存されます。しかし、その内容を操作する方法は、一般には公開されておらず、また、一覧を表示する機能も無いため、ユーザは、現在利用しているコンピュータでどのようなデータが保存されているのかさえ知ることはできません。本稿では、これらのデータを一覧するサンプルを作成し、その操作方法を解説します。
対象読者
- WindowsでC++を使用してネットワーク関連のプログラムを作成される方。
- Internet Explorerをサポートするプログラムに興味をお持ちの方。
必要な環境
- サンプルプログラムは、Windows 98/NT/2000/XPで動作します。
- サンプルコードは、C++ Builder6およびMicrosoft Visual C++ .NET 2003でコンパイルが可能です。
認証パスワードとオートコンプリート
Internet Explorerでは、認証(基本認証)が必要なWebサーバにアクセスした時、図1のダイアログを表示して、ユーザーにパスワードの入力を求めます。この時、あらかじめパスワードを記憶するように設定しておけば、以前に入力したユーザー名およびパスワードが自動的に挿入され、[OK]ボタンを押すだけで認証を完了することができます。非常に便利な機能なので利用している方は多いのではないでしょうか。
また、オートコンプリートが有効になっていれば、フォームで入力が必要になった場合に以前の入力が補完されることもご存じでしょう。どちらも、比較的便利な機能ですが、その設定は、図2のように、利用するか否か、また、記憶されたデータは、全てを削除するか否かの選択しかできません。
私は、これらのデータをきめ細かく整理するユーティリティ「HideSeek」を作成する際、データの保存形式や保存方法を調査し、その結果、データは暗号化されレジストリに格納されていることを確認しました。
そしてこの暗号化されたデータは、Protected Storageというサービスを経由して保存されていました。
Protected Storageとは
Windowsで起動されているサービスを確認すると、「Protected Storage」というサービスが起動していることを確認できます。サービス一覧の説明では、「秘密キーなどの重要なデータを格納するための保護された記憶域を提供し、許可のないサービス、許可のないプロセス、許可のないユーザーによるアクセスを防ぎます。」と記述されています。
このサービスは、IE 4.01以降導入されたもので、Windows 2000以降では標準サービスとなっています。IEでは、「認証パスワードの保存」「オートコンプリートの保存」「購読機能」で、このサービスを利用しています。なお、オートコンプリートの設定(図2)で表示される「Webアドレス」についてはIUrlHistoryStg2
インターフェースで扱われており、Protected Storageは使用されていません。
Protected Storageでは、各種のデータを次のレジストリを利用して保存しています。
HKEY_CURRENT_USER\Software\Microsoft\Protected Storage System Provider
しかし、前述のとおり、その内容は暗号化されておりレジストリエディタなどで編集することはできません。アプリケーションからは、IPStore
インターフェースを利用して、これらのデータの操作することになります。
IPStoreインターフェースの取得
IPStore
インターフェースは、Protected Storageを利用するための唯一のインターフェースです。このインターフェースは、通常利用されるCOMライブラリのCoCreateInstance
メソッドでは取得することができず、PStoreCreateInstance
メソッドを使用するようになっています。PStoreCreateInstance
メソッドは、「Pstorec.dll」から、LoadLibrary
およびGetProcAddress
を使用して関数へのポインタを取得して利用します。
// IPStore *PStore = NULL; // プロトタイプ宣言 typedef HRESULT (WINAPI *TPStoreCreateInstance) (IPStore **, DWORD, DWORD, DWORD); TPStoreCreateInstance PStoreCreateInstance; HMODULE hDll = LoadLibrary("pstorec.dll"); if(hDll!=NULL){ // PStoreCreateInstanceファンクションのポインタを取得 PStoreCreateInstance = (TPStoreCreateInstance) GetProcAddress(hDll,"PStoreCreateInstance"); if(PStoreCreateInstance!=NULL){ // IPStoreインターフェースの取得 if(S_OK != PStoreCreateInstance(&PStore,NULL,NULL,0)) PStore = NULL;// インターフェースの取得に失敗 } } }
IPStoreのデータ構造とメソッド
IPStoreのデータは、図3のような形式で保存されています。
最初の階層である「Type」については、IPStore
インターフェースのEnumTypes
メソッドでIEnumPStoreTypes
インターフェースを取得し、同インターフェースのNext
メソッドを利用して全てを列挙することができます。また、それぞれの「Type」の階層下の「SubType」についても、EnumSubtypes
メソッドを使用して同じ要領で取得が可能です。「SubType」の階層下である「ItemName」については、EnumItems
メソッドでIEnumPStoreItems
インターフェースを取得し、同インターフェースのNext
メソッドを利用して全てを列挙します。「ItemName」が取得できた後は、それぞれの「Item」をReadItem
メソッドを使用して取得する事になります。
結果的にIPStore
で保存されているデータは、ItemName
(LPWSTR
形式)およびItem
(BYTE
形式)で1セットとなっています。
本稿の目的とするIEの認証パスワードや、オートコンプリートのデータは、表1に示したType
(GUID
形式)およびSubType
(GUID
形式)で保存されています。従って、あらためてEnumTypes
やEnumSubtypes
でType
やSubType
を列挙する必要はありません。サンプルでは、これらのGUID
を直接指定してIEnumPStoreTypes
インターフェースを取得し、ItemName
を列挙することでデータを取得しています。
データの種類 | Type(GUID) | SubType(GUID) |
認証パスワード | 5E7E8100-9138-11D1-945A-00C04FC308FF | 00000000-0000-0000-0000-000000000000 |
オートコンプリート | E161255A-37C3-11D2-BCAA-00c04FD929DB | E161255A-37C3-11D2-BCAA-00c04FD929DB |
サンプルプログラムでは、汎用的にType
およびSubType
を与えてデータを取得するTPStore::Init
メソッドを使用していますので、詳しくはそちらを参照してください。また、IPStore
については、MSDNライブラリの『IPStore』にドキュメント化されています。インターフェースやメソッドの詳細については、こちらを確認して下さい。
EnumTypes
およびEnumSubtypes
を使用すると、Protected Storageを使用して格納されているすべてのデータを列挙することができます。各種のデータが取得できると思いますので、興味のある方は、ぜひ試してみてください。
Protected Storageの列挙
認証パスワード
次のコードは、認証パスワードのデータを取得するコードです。
GUID AuthGuid = { 0x5E7E8100, 0x9138,0x11D1, { 0x94, 0x5A, 0x00, 0xC0, 0x4F, 0xC3, 0x08, 0xFF} }; GUID AuthSubGuid = { 0x00000000, 0x0000, 0x0000, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} }; IEnumPStoreItems *pItems; // Type及びSubTypeを指定して、IEnumPStoreItemsを取得する if(0 == PStore->EnumItems(0, &AuthGuid, &AuthSubGuid,0,&pItems)){ LPWSTR ItemName; DWORD Fetched; while(TRUE){ //IEnumPStoreItemsのNextメソッドで一覧する pItems->raw_Next(1,&ItemName,&Fetched); if(!Fetched)// FeatchがTRUEの間、次のデータが存在する break; DWORD d; BYTE *Buffer; if(0 == PStore->ReadItem(PST_KEY_CURRENT_USER, &Guid,&SubGuid,ItemName,&d,&Buffer,NULL,0)){ // この時点で、ItemName及びBuffer(d はBufferに // 取得したサイズ)にデータが格納されています。 } } pItems->Release();// インターフェースの開放 }
最初に、IPStore
インターフェースのメソッドである、EnumItems
に表1で示したType
およびSubType
であるGUID
を与えて、IEnumPStoreItems
インターフェースを取得します。続いて同インターフェースのメソッドであるNext
を利用して階層下のItemName
を列挙します。Next
で列挙するループは、第3パラメータであるFetched
がFALSE
になった時点で終了です。
サンプルプログラムの実行画面から分かるとおり、保存されているデータは、「ホスト」「ポート番号」「認証名」「ユーザー」「パスワード」の5種類です。取得した、ItemName
およびItem
には、下記の形式でそれぞれのデータが保存されています。
ItemName
(LPWSTR
)Item
(BYTE
)
サンプルプログラムでは、CSampleDlg::AuthRefresh
(VC++)もしくは、TForm1::AuthRefresh
(C++Builder)でこの解釈を行っています。詳しくはそちらを参照してください。
WNetEnumCachedPasswords
を使用して、認証パスワードを取得できます。BOOL CALLBACK pce(PASSWORD_CACHE_ENTRY *x, DWORD) { char ItemName[2048]; char Item[2048]; if(x->nType==0x13){ //nTypeが0x13のデータは、IEの認証パスワード memmove(Item, x->abResource, x->cbResource); ItemName[x->cbResource] = 0; memmove(Data, x->abResource+x->cbResource, x->cbPassword); Item[x->cbPassword] = 0; // この時点で、ItemName及びItemにIPStoreの場合と // 同一の形式でデータが取得できる // ※ ただし、ItemNameはwchar_t * ではない事にに注意が必要 } return TRUE; } // WNetEnumCachedPasswordsへのポインタをmpr.dllから取得する HMODULE hDll = LoadLibrary("mpr.dll"); if(hDll!=NULL){ WORD (__stdcall *enp)(LPSTR, WORD, BYTE, void*, DWORD) = (WORD (__stdcall *)(LPSTR, WORD, BYTE, void*, DWORD)) GetProcAddress(hDll, "WNetEnumCachedPasswords"); if(enp) (*enp)(0,0, 0xff, pce, 0); }
WNetEnumCachedPasswords
については、既に色々な場所でその使用方法が公開されていますが、マイクロソフトの公式なドキュメントはありません。オートコンプリートの列挙
サンプルプログラムの実行画面を見ていただけると分かりますが、現在コンピュータ上で保存されているデータは次の3種類です。
- フォーム名
- オートコンプリート文字列
- 保存年月日
なお、ここで「フォーム名」とは、テキスト入力を促すフォームを表示する際にHTMLで記述される「<input type=text name=target_name >
」のname
に指定された名前(target_name
)です。サンプルプログラムの実行画面から一つのフォーム名に対し複数のオートコンプリート文字列が保存されている事が分かると思います。Internet Explorerでは、フォームへの入力途中にこのデータから先頭一致する文字列を表示しています。
オートコンプリートも認証パスワードの場合と同じ要領で、Type
およびSubType
に適切なGUID
を与えるだけでデータの取得が可能です。ただし、こちらのItemName
およびItem
への格納形式は、少し複雑になっています。
IEnumPStoreItems
インターフェースのNext
で取得できるItem
の名前は、オートコンプリートの場合、「フォーム名:StringData
」および「フォーム名:StringIndex
」の2つの形式になっています。つまり、2つのデータで1つのフォーム名に該当するデータが保存されている事になります。そして、「フォーム名:StringData
」には、フォーム名に該当する複数のオートコンプリート文字列が保存されており、「フォーム名:StringIndex
」には、その各データの保存されたオフセットおよび登録日付などのデータが保存されています。すなわち、2つのデータを取得した後、StringIndex
の情報を元にStringData
を読み出す必要があるのです。
図4は、StringIndex
のデータの一例です。この例を元に、StringIndex
の内部を説明してみましょう。まず最初の4バイトですが、ここでは、「0x57」「0x49」「0x43」「0x4B」となっていますが、これは、キャラクタで「WICK」となり定型の文字列です。次の4バイトは、「0x18」「0x00」「0x00」「0x00」でDWORD
のリトルインデアン形式で保存されたヘッダサイズ(「0x00000018」 24バイト)です。そして次の4バイトが「0x02」「0x00」「0x00」「0x00」であり、同じように格納されたデータの個数(「0x00000002」 2個)となっています。取得したヘッダサイズ分だけポインタを進めてみると、一つ一つのデータの情報が16バイトづつ並んでいます。1つのデータの情報は、最初の4バイトがデータのオフセット、次の8バイトが保存の日付です。つまり、例として示したStringIndex
では、StringData
に2つのデータが格納されており、それぞれ、オフセット「0x00」バイト目からと「0x16」バイト目から始まっていることが分かります。なお、StringData
内は、ワイド文字形式で格納されておりますので注意してください。
オートコンプリートのデータ解釈については、CSampleDlg::AutoRefresh
(VC++)もしくは、TForm1::AutoRefresh
(C++Builder)で実装しております。詳しくはそちらを参照してください。
IPStoreデータの削除・更新
今回作成したサンプルプログラムは、一覧の列挙しか行っておりません。しかし、IPStore
には、取得以外に削除や追加のメソッドも用意されていますので、これらを利用して同データを自由に変更することが可能です。なお、オートコンプリートのデータについては、1つのフォームに対する複数のデータが1つセットとして管理されていますので、その一部を削除したり編集したりする場合は、いったん全てのデータ取得し、データ構造を再構築して格納する必要があるため、やや注意が必要です。
まとめ
本稿では、Internet Explorerの保存する非公開のデータについて解説しました。本来は秘匿されているはずのデータを操作する方法ですので、アプリケーション作成の際にこの技術を利用する場合は、セキュリティ上の問題を十分に考慮してください。
参考資料
- MSDNライブラリ 『IPStore』
- THE CODE PROJECT 『Protected Storage』 Hirosh Joseph 著、2004年2月
- SapporoWorks 『HideSeek(本稿で解説したIPStoreなどを操作して各種データを表示・編集するツール)』