AppContainerでデスクトップアプリを起動してみた

Windows8以降にAppContainerというsandbox機能が導入されているのをご存じだろうか?
このAppContainerは主にWindowsストアアプリの実行環境として使用されているのだが、最近巷を騒がせているIEの脆弱性もAppContainerを有効にすることで回避することができるらしいと聞く。
ということは、デスクトップアプリであってもAppContainerでセキュリティが向上できるかもしれない。
そこで今回はAppContainer上でデスクトップアプリを実行する方法を解説してみたいと思う。

本記事で紹介するコードやコンパイル済みのexeなどを用意しておいたので、実際に動かしてみたい方はどうぞ。
ただし、後始末やここでは解説しない機能の使用なども含んでいるため、本質的ではないコードが多いことをあらかじめ断っておく。
http://wordpress.click3.org/garakuta/StartAppContainer.zip

この記事の目次

割り当てるCapabilityを取得する

AppContainer上で動作するアプリは既存のIntegrity Levelによる制限とは違い、Capabilityとして宣言しておくことで追加の権限を得ることができる。
そこでまずは動作させたいアプリに付与する権限を取得するところから始めよう。
なお、具体的にどのような権限があるかは解説しないので、詳しくはMSDNを参照してほしい。

実際にCapabilityを取得するコードは以下のようになる。

bool SetCapability(const WELL_KNOWN_SID_TYPE type, std::vector<SID_AND_ATTRIBUTES> &list, std::vector<SHARED_SID> &sidList) {
  SHARED_SID capabilitySid(new unsigned char[SECURITY_MAX_SID_SIZE]);
  DWORD sidListSize = SECURITY_MAX_SID_SIZE;
  if (::CreateWellKnownSid(type, NULL, capabilitySid.get(), &sidListSize) == FALSE) {
    return false;
  }
  if (::IsWellKnownSid(capabilitySid.get(), type) == FALSE) {
    return false;
  }
  SID_AND_ATTRIBUTES attr;
  attr.Sid = capabilitySid.get();
  attr.Attributes = SE_GROUP_ENABLED;
  list.push_back(attr);
  sidList.push_back(capabilitySid);
  return true;
}

前後にごちゃごちゃ長いものがついているが、本質的にはCreateWellKnownSidでSIDを取得し、SID_AND_ATTRIBUTES構造体に詰めて返している。
IsWellKnownSidはSIDがtypeと一致するかを調べるもので、間違いなく成功するので意味はないassertの一種だと思ってほしい。
その他はメモリーの開放といった後始末になる。

まずCreateWellKnownSidはシステム定義のSIDを取得するAPIでCapabilityのSIDもこれで取得できる。
取得するSIDの種類を表すWELL_KNOWN_SID_TYPEのうちWinCapabilityから始まるものがそれだ。

次にSID_AND_ATTRIBUTESだが、これは何のためのSIDなのかやどういう意図で使用するかなどを表す。
今回はCapabilityを追加したいのでAttributesにSE_GROUP_ENABLEDを設定したものを使う。

AppContainerのプロファイルを作成する

次はAppContainerのプロファイルを作成しよう。
プロファイルも識別子としてはSIDを用いるので、今回取得するのもSIDとなる。

  PSID sidImpl;
  DeleteAppContainer deleteAppContainer(AppContainerName);
  if (FAILED(::CreateAppContainerProfile(AppContainerName, AppContainerName, AppContainerName, (capabilities.empty() ? NULL : &capabilities.front()), capabilities.size(), &sidImpl))) {
    return false;
  }
  const SHARED_SID sid = ToSharedSID2(sidImpl);

CreateAppContainerProfileがプロファイルを作成するAPIで、その前後は後始末に関係する。
CreateAppContainerProfileのプロトタイプは以下のようになる。

HRESULT WINAPI CreateAppContainerProfile(
  _In_   PCWSTR pszAppContainerName,
  _In_   PCWSTR pszDisplayName,
  _In_   PCWSTR pszDescription,
  _In_   PSID_AND_ATTRIBUTES pCapabilities,
  _In_   DWORD dwCapabilityCount,
  _Out_  PSID *ppSidAppContainerSid
);

第一引数はプロファイルの識別子で、AppContainer固有のアプリケーションフォルダのパスなどで使用される。
第二第三は表示用なので割愛。
第四第五引数はCapabilityを指定するもののようだが、ここでは指定しなくとも問題なく権限は付加される。
おそらくはモダンアプリなどexplorerから起動出来るAppContainerのアプリの場合、ここで設定したCapabilityを用いて起動するのだと思われる。
コード中で指定してあるのはあくまで念のためだ。
第六引数は生成したプロファイルのSIDを受けるポインターとなる。

作成したプロファイルでアプリを起動する

さて、プロファイルも無事生成出来たのでいよいよアプリを実行しよう。
少々長いが、アプリの起動は以下のようになる。

  const unsigned int attrCount = 3;
  SIZE_T size;
  ::InitializeProcThreadAttributeList(NULL, attrCount, 0, &size);
  const SHARED_ATTR_LIST attrList = ToSharedAttributeList(reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(new unsigned char[size]));
  if (::InitializeProcThreadAttributeList(attrList.get(), attrCount, 0, &size) == FALSE) {
    return false;
  }
  SECURITY_CAPABILITIES sc;
  sc.AppContainerSid = sid.get();
  sc.Capabilities = (capabilities.empty() ? NULL : &capabilities.front());
  sc.CapabilityCount = capabilities.size();
  sc.Reserved = 0;
  if (::UpdateProcThreadAttribute(attrList.get(), 0, PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES, &sc, sizeof(sc), NULL, NULL) == FALSE) {
    return false;
  }
  STARTUPINFOEXW startupInfoEx = { 0 };
  startupInfoEx.StartupInfo.cb = sizeof(STARTUPINFOEXW); // StartupInfoのサイズではなくstartupInfoExのサイズである点に注意
  startupInfoEx.lpAttributeList = attrList.get();
  PROCESS_INFORMATION pi = { 0 };
  if (::CreateProcessW(path.wstring().c_str(), NULL, NULL, NULL, FALSE, EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, reinterpret_cast<LPSTARTUPINFO>(&startupInfoEx), &pi) == FALSE) {
    std::wcout << GetLastError() << std::endl;
    return false;
  }
  ::WaitForSingleObject(pi.hProcess, INFINITE);
  ::CloseHandle(pi.hThread);
  ::CloseHandle(pi.hProcess);

InitializeProcThreadAttributeListでLPPROC_THREAD_ATTRIBUTE_LISTを初期化し、UpdateProcThreadAttributeでプロファイルのsidとCapabilityを設定してCreateProcessWでアプリを起動している。
その他諸々はほかと同じく後始末になる。

InitializeProcThreadAttributeListは見たまま単に初期化を行っているだけだ。
設定する属性の数は念のため3にしてあるが、おそらく1でも動作する。
UpdateProcThreadAttributeもみたままにLPPROC_THREAD_ATTRIBUTE_LISTにsidを設定しているだけだ。
Capabilityはここで指定してあればプロファイルに指定がなくともちゃんと割り振られて動作する。
CreateProcessWはEXTENDED_STARTUPINFO_PRESENTを指定してやるとSTARTUPINFOでLPPROC_THREAD_ATTRIBUTE_LISTを受けてくれるようになる。
あとは単にSTARTUPINFOEXWにLPPROC_THREAD_ATTRIBUTE_LISTを設定して起動しているだけだ。

実際に起動してみる

http://wordpress.click3.org/garakuta/StartAppContainer.zipに完成品があるのでDLしてもらって、中のTestAppContainer.exeを普通にダブルクリックで起動してみてほしい。
どうだろうか?おそらくは「このアプリケーションはアプリ コンテナーのコンテキストでのみ実行できます。」と表示されて起動しなかったと思う。
これは/APPCONTAINERオプションを指定してコンパイルされたものでAppContainer以外では動作しない。
そこで次はTestAppContainer2.exeを起動してみてほしい。
こちらは単に/APPCONTAINERオプションを外しただけのものになる。
動作としてはいろいろなフォルダにアクセスして中身を列挙するというものだ。

では完成品であるStartAppContainer.exeにTestAppContainer.exeをドラッグ&ドロップしてみてほしい。
今度はちゃんと起動したと思う。
また動作もTestAppContainer2.exeを起動したときと違い、見えないフォルダがあったり、そもそも別フォルダを指していたりしたと思う。
それがAppContainerによるサンドボックス化された環境ということになる。

また、付与したCapabilityによる差が見たい人はTestWinRT.exeを試してみてほしい。
こちらはWinRTのAPIを用いている以外はTestAppContainer.exeと同様だが、AppContainerとして起動するとミュージックフォルダが取得できるようになっているはずだ。
これはCapabilityでMusicLibraryを付与したためにアクセス可能になったことを表している。
TestAppContainer.exeで見えていないのはCapabilityがあくまでWinRTのAPIにしか影響しないためだ。

その他cmd.exeをAppContainerで起動してみるなどもできるのでいろいろ試してみてほしい。

終わりに

どうだっただろうか。
今回はあくまでAppContainerで起動するところまでを目標としたが、zipの中では紹介していないAPIを使用するコードなども入れてあり、また次の記事では関連APIについてもまとめているので、興味があれば見ていってほしい。

最初にも書いたがIEもAppContainerにより脆弱性が見つかってもできるだけ他所に波及しないようにと作られている。
この記事を読んでそういうことに興味を持ってくれる人が一人でも増えたら幸いだ。

ではまた何か機会があればその時に。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です