セキュリティ設定可能なオブジェクトには、ファイルやプロセスなど様々な種類があるが、そこにウインドウというものは含まれていない。 これが意味するのは、CreateWindowExのような関数にはセキュリティ記述子を指定できないということであり、 特定アカウントからのウインドウアクセスは制御できない。 しかし、Windows VistaからはUIPIという仕組みが登場し、整合レベルベースでウインドウメッセージを調整するようになった。
Windowsメッセージングサブシステムもまた、「ユーザーインターフェース特権の分離」を実装するために、整合性レベルを保持しています。 このサブシステムは、より高い整合性レベルを持つプロセスが所有するウインドウに対して、プロセスがウインドウメッセージを送信するのをブロックすることで、UIPIを実装しています。
(「インサイドWindows 第7版 上」p.739より引用)
ウインドウにおけるセキュリティで最も懸案になりがちなのは、 ウインドウ内に書き込まれた文字列を不正なプログラムによって書き換えられるというものだろう。 しかし、UIPIでは整合レベルが低いプロセスからのこうした書き換えはブロックされる。
// ウインドウクラスを元に、メモ帳のウインドウを検索
HWND hwndNotepad = FindWindow(L"notepad", NULL);
// メモ帳のウインドウからエディットコントロールを取得
HWND hwndEdit;
EnumChildWindows(hwndNotepad, EnumChildProc, (LPARAM)&hwndEdit);
// エディットコントロールにテキストを設定
BOOL bResult = SendMessage(hwndEdit, WM_SETTEXT, 0, (LPARAM)L"text");
このコードでは、メモ帳のウインドウに対してWM_SETTEXTで書き込みを行っている。 もしかしたら、ユーザーを騙すような悪意のある文字列が書き込まれるかもしれない。 しかし、メモ帳が高い整合性レベルで動作しており、クライアントがそれより低い場合は、 UIPIによってWM_SETTEXTの送信はブロックされる。 UIPIは一定のメッセージ以外は規制するようになっており、その一覧は以下である。
ただし、以下に示す情報メッセージは例外です。WM_NULL, WM_MOVE, WM_SIZE, WM_GETTEXT, WM_GETTEXTLENGTH, WM_GETHOTKEY, WM_GETICON, WM_RENDERFORMAT, WM_DRAWCLIPBOARD, WM_CHANGECBCHAIN, WM_THEMECHANGED
(「インサイドWindows 第7版 上」p.739より引用)
WM_SETTEXTはこの一覧に含まれないため規制されるわけだが、 WM_GETTEXTは許可されている事に注目したい。 つまり、以下のように低い整合性レベルのプロセスから高い整合性レベルのウインドウに書き込まれた文字列は取得できる。
TCHAR szBuf[256];
bResult = SendMessage(hwndEdit, WM_GETTEXT, 256, (LPARAM)szBuf);
UIPIによって、具体的にどういった恩恵が生じているといえるのだろうか。
整合性レベルのこの用途は、標準ユーザーのプロセスが、昇格したプロセスのウインドウに入力することや、シャッター攻撃(不正なメッセージを送信することで、内部バッファーフローを引き起こし、 昇格したプロセスの特権レベルでコードを実行するといった攻撃)をブロックします。
(「インサイドWindows 第7版 上」p.739より引用)
入力処理は、先ほど示したWM_SETTEXTが該当すると思われる。WM_CLOSEでウインドウを閉じることや、WM_COMMANDでボタンが押されたように見せることなども含まれるかもしれない。 バッファーフローは、WM_COPYDATAで生じることがある。
TCHAR szData[] = TEXT("message");
COPYDATASTRUCT data = {0};
data.cbData = sizeof(szData);
data.lpData = szData;
SendMessage(hwndTarget, WM_COPYDATA, (WPARAM)hwnd, (LPARAM)&data);
WM_COPYDATAでは、COPYDATASTRUCT構造体にデータとサイズを設定する。 このサイズは送信側が自由に設定できるから、もし受信側がデータを受け取るバッファサイズを静的に定義していると、 バッファーフローが起きる可能性がある。 この他のケースでは、WM_DROPFILESがある。これはドラッグアンドドロップされたファイルを受け取る際のメッセージだが、 送信側が不正なファイルを送り付け、読み込み時にバッファーフローを狙うなどがある。
既に述べたように、UIPIは一定のメッセージ送信以外はブロックするわけだが、 その規則には例外を設定することができる。
プロセス(整合性レベル「中」以上で実行中のプロセスのみ)は、ChangeWindowMessageFilterEx APIを呼び出すことで、追加的なメッセージがこのガードを追加できるように選択できます。 この関数は、一般的にWindowsのネイティブな共通コントロールの外部で通信する、カスタムコントロールが必要とするメッセージを追加するために使用されます。
(「インサイドWindows 第7版 上」p.739より引用)
共通コントロール(以後コモンコントロールと記述)とは、ボタンやコンボボックスなどWindowsに標準で用意されているウインドウである。 これに対し、カスタムコントロールとは、アプリケーション定義の動作を行うウインドウである。 以下に、カスタムコントロールの作成とメッセージ処理の例を示す。
void InitInstance()
{
// カスタムのウインドウプロシージャとウインドウクラスの名前を指定して、ウインドウクラスを登録
wc.lpfnWndProc = CustomControlProc;
wc.lpszClassName = lpszClassName;
if (RegisterClassEx(&wc) == 0)
return NULL;
// ウインドウクラスの名前を指定してウインドウを作成
hwnd = CreateWindowEx(0, lpszClassName, NULL, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, HWND_MESSAGE, NULL, NULL, NULL);
}
// カスタムのウインドウプロシージャの実装
LRESULT CALLBACK CustomControlProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg) {
case WM_CREATE:
// WM_APPの受信を許可
ChangeWindowMessageFilterEx(hwnd, WM_APP, MSGFLT_ALLOW, NULL);
return 0;
case WM_APP:
// 外部プロセスがSendMessage(hwnd, WM_APP, 0, 0);を実行した場合、ここが実行される
return 0;
default:
break;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
ウインドウ作成過程でWM_CREATEが発行されるので、ここでWM_APPというメッセージの使用を許可している。 これによりhwndで識別されるウインドウは、 昇格されていない外部プロセスからのWM_APPを受信できる。 外部との通信では、用途を自由に決められるWM_APPを使用するのが通例になっている。 WM_USERというメッセージもあるが、こちらはコモンコントロールが内部で使用している。
先のコードで、ExのついていないChangeWindowMessageFilterを呼び出した場合は、カスタムコントロールだけでなく、 このプロセスに存在する他のコントロールもWM_APPを受信できることになる。 WM_APPを他のコントロールが使用していることはないはずだが、 WM_USERはコモンコントロールが使用しているわけだから、 WM_USERをChangeWindowMessageFilterに指定するのは問題といえる。 他のプロセスが悪意を持ってWM_USERを送りつける余地を与えてしまうためである。
ユーザー入力を補助するようなアプリケーションにとって、UIPIは足枷になることがある。 たとえば、system32フォルダに存在するosk.exeは画面にキーボード画像を表示し、 ユーザーがキーをマウスでクリックすると、そのキーの入力を背後のウインドウに届ける。 もし、背後のウインドウが管理者プロセスによって作成されていても、この動作はユーザービリティ的に成立すべきだから、 osk.exeにはUIPIを無効にする設定が組み込まれている。
それらのプロセスではUIアクセスを有効化できます。そのためのフラグは実行可能イメージのマニフェストファイルの中に存在することができ
(「インサイドWindows 第7版 上」p.739より引用)
exeの作成時にはマニフェストファイルというものも含めることができるが、 そのファイルの中のuiaccess属性がtrueになっていると考えていただきたい。 これがUIPIを無効にする目印となる。 しかし、この設定だけでUIPIを無効にできるならば、他のアプリケーションも容易に真似できてしまうため、 当然ながら他の設定も必要である。
プロセスにこのフラグをセットする場合、その実行可能イメージは署名されている必要もあり、%SystemRoot%や%ProgramFiles%といった安全な場所に配置される必要もあります。
(「インサイドWindows 第7版 上」p.739より引用)
署名が必須という点が敷居高いといえる。 しかし、自己署名証明書でも問題ないため、テスト時ではこうした証明書で代用できる。 ところで、UIアクセスが有効なプロセスの整合性レベルはどのようになっているのだろうか。
標準ユーザーアカウントで開始された場合、プロセスは暗黙的に「中」より高い整合性レベル(0x2000から0x3000の間)で実行され、 管理者アカウントで開始された場合、整合性レベル「高」で実行されます。
(「インサイドWindows 第7版 上」p.739より引用)
まず、0x2000などの定義を確認しておく。
#define SECURITY_MANDATORY_LABEL_AUTHORITY {0,0,0,0,0,16}
#define SECURITY_MANDATORY_UNTRUSTED_RID (0x00000000L)
#define SECURITY_MANDATORY_LOW_RID (0x00001000L)
#define SECURITY_MANDATORY_MEDIUM_RID (0x00002000L)
#define SECURITY_MANDATORY_MEDIUM_PLUS_RID (SECURITY_MANDATORY_MEDIUM_RID + 0x100)
#define SECURITY_MANDATORY_HIGH_RID (0x00003000L)
#define SECURITY_MANDATORY_SYSTEM_RID (0x00004000L)
#define SECURITY_MANDATORY_PROTECTED_PROCESS_RID (0x00005000L)
整合性レベル毎にRIDが定義されている。 MEDIUM_RID、MEDIUM_PLUS_RID、HIGH_RIDのどれになるか、コードで検証する。
// osk.exeのウインドウクラス名でosk.exeのウインドウを取得
hwnd = FindWindow(L"OSKMainClass", NULL);
// ウインドウからプロセスIDを取得
GetWindowThreadProcessId(hwnd, &dwProcessId);
// プロセスのハンドルを取得。呼び出し側が管理者プロセスでなければ開けない。
// ちなみに、CreateProcessでosk.exeを起動することは管理者でもできない。(ERROR_ELEVATION_REQUIREDが返る)
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwProcessId);
// プロセスからトークンを取得
OpenProcessToken(hProcess, TOKEN_QUERY, &hTargetToken);
// トークンから整合性レベルを取得
GetTokenInformation(hToken, TokenIntegrityLevel, pMandatoryLabel, ...);
pMandatoryLabel->Label.Sidで得られるSIDを解析すると、SECURITY_MANDATORY_HIGH_RIDという結果が出た。 よって、0x3000の間という制約は満たしている。
-
CreateRestrictedToken 整合レベル及びUIPIの概念が登場する以前でも、CreateRestrictedTokenを使用してAdministratorsを無効にしたトークンは作成できた。 ただし、そのトークンが割り当てられた制限されたプロセスであっても、SendMessageやPostMessageが使えることを警告している。 ここで提唱されている解決策は、デフォルトとは異なるデスクトップで実行するというものだった。 現在のWindowsでは同じデスクトップ上で実行されていても問題ないため、この警告は昔の名残であると考えられる。
-
CWMFEx ChangeWindowMessageFilterExの使用例。CWMFEX_CONTROLという独自メッセージは、WM_APP+1で定義されている。
-
DesktopAutomationDismiss GetTokenInformationにTokenUIAccessを指定して、UIアクセスを確認する例。
-
UIAutomationSimpleProvider UIAutomationのプロバイダ作成例。 クライアントはメッセージではなく、IInvokeProvider::Invokeでボタンの押下を再現できる。
-
User Account Control: Only elevate UIAccess applications that are installed in secure locations 日本語訳は脅威の防止